SurfaceFlinger: use config groups

Composer 2.4 adds a new attribute for configs groups. This change
groups configs according to their group and store them in
RefreshRateConfigs.

Test: rev up composer to 2.4 and test refresh rate switching
Test: adb shell /data/nativetest64/libsurfaceflinger_unittest/libsurfaceflinger_unittest
Test: adb shell /data/nativetest64/SurfaceFlinger_test/SurfaceFlinger_test
Bug: 141329414
Fixes: 139751853
Change-Id: Ic0bcd3da4bf6b73efa11a60c2594948ce030362f
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 8d9adc8..ff800c3 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -36,6 +36,7 @@
 #include <utils/Trace.h>
 
 #include "EventThread.h"
+#include "HwcStrongTypes.h"
 
 using namespace std::chrono_literals;
 
@@ -101,10 +102,11 @@
     return event;
 }
 
-DisplayEventReceiver::Event makeConfigChanged(PhysicalDisplayId displayId, int32_t configId) {
+DisplayEventReceiver::Event makeConfigChanged(PhysicalDisplayId displayId,
+                                              HwcConfigIndexType configId) {
     DisplayEventReceiver::Event event;
     event.header = {DisplayEventReceiver::DISPLAY_EVENT_CONFIG_CHANGED, displayId, systemTime()};
-    event.config.configId = configId;
+    event.config.configId = configId.value();
     return event;
 }
 
@@ -290,7 +292,7 @@
     mCondition.notify_all();
 }
 
-void EventThread::onConfigChanged(PhysicalDisplayId displayId, int32_t configId) {
+void EventThread::onConfigChanged(PhysicalDisplayId displayId, HwcConfigIndexType configId) {
     std::lock_guard<std::mutex> lock(mMutex);
 
     mPendingEvents.push_back(makeConfigChanged(displayId, configId));
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index a029586..a42546c 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -33,6 +33,7 @@
 #include <private/gui/BitTube.h>
 
 #include <utils/Errors.h>
+#include "HwcStrongTypes.h"
 
 // ---------------------------------------------------------------------------
 namespace android {
@@ -109,7 +110,7 @@
     virtual void onHotplugReceived(PhysicalDisplayId displayId, bool connected) = 0;
 
     // called when SF changes the active config and apps needs to be notified about the change
-    virtual void onConfigChanged(PhysicalDisplayId displayId, int32_t configId) = 0;
+    virtual void onConfigChanged(PhysicalDisplayId displayId, HwcConfigIndexType configId) = 0;
 
     virtual void dump(std::string& result) const = 0;
 
@@ -146,7 +147,7 @@
 
     void onHotplugReceived(PhysicalDisplayId displayId, bool connected) override;
 
-    void onConfigChanged(PhysicalDisplayId displayId, int32_t configId) override;
+    void onConfigChanged(PhysicalDisplayId displayId, HwcConfigIndexType configId) override;
 
     void dump(std::string& result) const override;
 
diff --git a/services/surfaceflinger/Scheduler/HwcStrongTypes.h b/services/surfaceflinger/Scheduler/HwcStrongTypes.h
new file mode 100644
index 0000000..cfbbdfe
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/HwcStrongTypes.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2019 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 "StrongTyping.h"
+
+namespace android {
+
+// Strong types for the different indexes as they are referring to a different base.
+using HwcConfigIndexType = StrongTyping<int, struct HwcConfigIndexTypeTag, Compare, Add, Hash>;
+using HwcConfigGroupType = StrongTyping<int, struct HwcConfigGroupTypeTag, Compare>;
+
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 8b71728..146ec1b 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -39,7 +39,7 @@
 namespace {
 
 bool isLayerActive(const Layer& layer, const LayerInfo& info, nsecs_t threshold) {
-    return layer.isVisible() && (info.isHDR() || info.getLastUpdatedTime() >= threshold);
+    return layer.isVisible() && info.getLastUpdatedTime() >= threshold;
 }
 
 bool traceEnabled() {
@@ -69,7 +69,7 @@
     mLayerInfos.emplace_back(layer, std::move(info));
 }
 
-void LayerHistory::record(Layer* layer, nsecs_t presentTime, bool isHDR, nsecs_t now) {
+void LayerHistory::record(Layer* layer, nsecs_t presentTime, nsecs_t now) {
     std::lock_guard lock(mLock);
 
     const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),
@@ -78,7 +78,6 @@
 
     const auto& info = it->second;
     info->setLastPresentTime(presentTime, now);
-    info->setIsHDR(isHDR);
 
     // Activate layer if inactive.
     if (const auto end = activeLayers().end(); it >= end) {
@@ -89,7 +88,6 @@
 
 LayerHistory::Summary LayerHistory::summarize(nsecs_t now) {
     float maxRefreshRate = 0;
-    bool isHDR = false;
 
     std::lock_guard lock(mLock);
 
@@ -108,13 +106,12 @@
                 trace(layer, std::round(refreshRate));
             }
         }
-        isHDR |= info->isHDR();
     }
     if (CC_UNLIKELY(mTraceEnabled)) {
-        ALOGD("%s: maxRefreshRate=%.2f, isHDR=%d", __FUNCTION__, maxRefreshRate, isHDR);
+        ALOGD("%s: maxRefreshRate=%.2f", __FUNCTION__, maxRefreshRate);
     }
 
-    return {maxRefreshRate, isHDR};
+    return {maxRefreshRate};
 }
 
 void LayerHistory::partitionLayers(nsecs_t now) {
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index bd9aca1..745c4c1 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -46,11 +46,10 @@
     void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate);
 
     // Marks the layer as active, and records the given state to its history.
-    void record(Layer*, nsecs_t presentTime, bool isHDR, nsecs_t now);
+    void record(Layer*, nsecs_t presentTime, nsecs_t now);
 
     struct Summary {
         float maxRefreshRate; // Maximum refresh rate among recently active layers.
-        bool isHDR;           // True if any recently active layer has HDR content.
     };
 
     // Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index b86709f..cb81ca2 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -140,9 +140,6 @@
     // updated time, the updated time is the present time.
     void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now);
 
-    bool isHDR() const { return mIsHDR; }
-    void setIsHDR(bool isHDR) { mIsHDR = isHDR; }
-
     bool isRecentlyActive(nsecs_t now) const { return mPresentTimeHistory.isRecentlyActive(now); }
     bool isFrequent(nsecs_t now) const { return mPresentTimeHistory.isFrequent(now); }
 
@@ -167,7 +164,6 @@
     nsecs_t mLastPresentTime = 0;
     RefreshRateHistory mRefreshRateHistory{mHighRefreshRate};
     PresentTimeHistory mPresentTimeHistory;
-    bool mIsHDR = false;
 };
 
 } // namespace scheduler
diff --git a/services/surfaceflinger/Scheduler/PhaseOffsets.cpp b/services/surfaceflinger/Scheduler/PhaseOffsets.cpp
index 6be88f8..12832a6 100644
--- a/services/surfaceflinger/Scheduler/PhaseOffsets.cpp
+++ b/services/surfaceflinger/Scheduler/PhaseOffsets.cpp
@@ -48,16 +48,18 @@
             getProperty("debug.sf.phase_offset_threshold_for_next_vsync_ns")
                     .value_or(std::numeric_limits<nsecs_t>::max());
 
-    const Offsets defaultOffsets = getDefaultOffsets(thresholdForNextVsync);
-    const Offsets highFpsOffsets = getHighFpsOffsets(thresholdForNextVsync);
-
-    mOffsets.insert({RefreshRateType::DEFAULT, defaultOffsets});
-    mOffsets.insert({RefreshRateType::PERFORMANCE, highFpsOffsets});
+    mDefaultOffsets = getDefaultOffsets(thresholdForNextVsync);
+    mHighFpsOffsets = getHighFpsOffsets(thresholdForNextVsync);
 }
 
-PhaseOffsets::Offsets PhaseOffsets::getOffsetsForRefreshRate(
-        RefreshRateType refreshRateType) const {
-    return mOffsets.at(refreshRateType);
+PhaseOffsets::Offsets PhaseOffsets::getOffsetsForRefreshRate(float fps) const {
+    // TODO(145561086): Once offsets are common for all refresh rates we can remove the magic
+    // number for refresh rate
+    if (fps > 65.0f) {
+        return mHighFpsOffsets;
+    } else {
+        return mDefaultOffsets;
+    }
 }
 
 void PhaseOffsets::dump(std::string& result) const {
@@ -80,13 +82,13 @@
     const auto earlyAppOffsetNs = getProperty("debug.sf.early_app_phase_offset_ns");
     const auto earlyGlAppOffsetNs = getProperty("debug.sf.early_gl_app_phase_offset_ns");
 
-    return {{RefreshRateType::DEFAULT, earlySfOffsetNs.value_or(sfVsyncPhaseOffsetNs),
+    return {{earlySfOffsetNs.value_or(sfVsyncPhaseOffsetNs),
              earlyAppOffsetNs.value_or(vsyncPhaseOffsetNs)},
 
-            {RefreshRateType::DEFAULT, earlyGlSfOffsetNs.value_or(sfVsyncPhaseOffsetNs),
+            {earlyGlSfOffsetNs.value_or(sfVsyncPhaseOffsetNs),
              earlyGlAppOffsetNs.value_or(vsyncPhaseOffsetNs)},
 
-            {RefreshRateType::DEFAULT, sfVsyncPhaseOffsetNs, vsyncPhaseOffsetNs},
+            {sfVsyncPhaseOffsetNs, vsyncPhaseOffsetNs},
 
             thresholdForNextVsync};
 }
@@ -104,13 +106,13 @@
     const auto highFpsEarlyGlAppOffsetNs =
             getProperty("debug.sf.high_fps_early_gl_app_phase_offset_ns");
 
-    return {{RefreshRateType::PERFORMANCE, highFpsEarlySfOffsetNs.value_or(highFpsLateSfOffsetNs),
+    return {{highFpsEarlySfOffsetNs.value_or(highFpsLateSfOffsetNs),
              highFpsEarlyAppOffsetNs.value_or(highFpsLateAppOffsetNs)},
 
-            {RefreshRateType::PERFORMANCE, highFpsEarlyGlSfOffsetNs.value_or(highFpsLateSfOffsetNs),
+            {highFpsEarlyGlSfOffsetNs.value_or(highFpsLateSfOffsetNs),
              highFpsEarlyGlAppOffsetNs.value_or(highFpsLateAppOffsetNs)},
 
-            {RefreshRateType::PERFORMANCE, highFpsLateSfOffsetNs, highFpsLateAppOffsetNs},
+            {highFpsLateSfOffsetNs, highFpsLateAppOffsetNs},
 
             thresholdForNextVsync};
 }
diff --git a/services/surfaceflinger/Scheduler/PhaseOffsets.h b/services/surfaceflinger/Scheduler/PhaseOffsets.h
index 2c52432..7747f0c 100644
--- a/services/surfaceflinger/Scheduler/PhaseOffsets.h
+++ b/services/surfaceflinger/Scheduler/PhaseOffsets.h
@@ -32,7 +32,6 @@
 class PhaseOffsets {
 public:
     using Offsets = VSyncModulator::OffsetsConfig;
-    using RefreshRateType = RefreshRateConfigs::RefreshRateType;
 
     virtual ~PhaseOffsets();
 
@@ -43,9 +42,9 @@
     }
 
     virtual Offsets getCurrentOffsets() const = 0;
-    virtual Offsets getOffsetsForRefreshRate(RefreshRateType) const = 0;
+    virtual Offsets getOffsetsForRefreshRate(float fps) const = 0;
 
-    virtual void setRefreshRateType(RefreshRateType) = 0;
+    virtual void setRefreshRateFps(float fps) = 0;
 
     virtual void dump(std::string& result) const = 0;
 };
@@ -57,18 +56,14 @@
     PhaseOffsets();
 
     // Returns early, early GL, and late offsets for Apps and SF for a given refresh rate.
-    Offsets getOffsetsForRefreshRate(RefreshRateType) const override;
+    Offsets getOffsetsForRefreshRate(float fps) const override;
 
     // Returns early, early GL, and late offsets for Apps and SF.
-    Offsets getCurrentOffsets() const override {
-        return getOffsetsForRefreshRate(mRefreshRateType);
-    }
+    Offsets getCurrentOffsets() const override { return getOffsetsForRefreshRate(mRefreshRateFps); }
 
     // This function should be called when the device is switching between different
     // refresh rates, to properly update the offsets.
-    void setRefreshRateType(RefreshRateType refreshRateType) override {
-        mRefreshRateType = refreshRateType;
-    }
+    void setRefreshRateFps(float fps) override { mRefreshRateFps = fps; }
 
     // Returns current offsets in human friendly format.
     void dump(std::string& result) const override;
@@ -77,9 +72,10 @@
     static Offsets getDefaultOffsets(nsecs_t thresholdForNextVsync);
     static Offsets getHighFpsOffsets(nsecs_t thresholdForNextVsync);
 
-    std::atomic<RefreshRateType> mRefreshRateType = RefreshRateType::DEFAULT;
+    std::atomic<float> mRefreshRateFps = 0;
 
-    std::unordered_map<RefreshRateType, Offsets> mOffsets;
+    Offsets mDefaultOffsets;
+    Offsets mHighFpsOffsets;
 };
 
 } // namespace impl
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 7dc98cc..23fb96a 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -13,135 +13,169 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+// #define LOG_NDEBUG 0
 #include "RefreshRateConfigs.h"
 
 namespace android::scheduler {
+
+using AllRefreshRatesMapType = RefreshRateConfigs::AllRefreshRatesMapType;
 using RefreshRate = RefreshRateConfigs::RefreshRate;
-using RefreshRateType = RefreshRateConfigs::RefreshRateType;
 
 // Returns the refresh rate map. This map won't be modified at runtime, so it's safe to access
 // from multiple threads. This can only be called if refreshRateSwitching() returns true.
 // TODO(b/122916473): Get this information from configs prepared by vendors, instead of
 // baking them in.
-const std::map<RefreshRateType, RefreshRate>& RefreshRateConfigs::getRefreshRateMap() const {
-    LOG_ALWAYS_FATAL_IF(!mRefreshRateSwitchingSupported);
-    return mRefreshRateMap;
-}
+const RefreshRate& RefreshRateConfigs::getRefreshRateForContent(float contentFramerate) const {
+    std::lock_guard lock(mLock);
+    // Find the appropriate refresh rate with minimal error
+    auto iter = min_element(mAvailableRefreshRates.cbegin(), mAvailableRefreshRates.cend(),
+                            [contentFramerate](const auto& lhs, const auto& rhs) -> bool {
+                                return std::abs(lhs->fps - contentFramerate) <
+                                        std::abs(rhs->fps - contentFramerate);
+                            });
 
-const RefreshRate& RefreshRateConfigs::getRefreshRateFromType(RefreshRateType type) const {
-    if (!mRefreshRateSwitchingSupported) {
-        return getCurrentRefreshRate().second;
-    } else {
-        auto refreshRate = mRefreshRateMap.find(type);
-        LOG_ALWAYS_FATAL_IF(refreshRate == mRefreshRateMap.end());
-        return refreshRate->second;
-    }
-}
+    // Some content aligns better on higher refresh rate. For example for 45fps we should choose
+    // 90Hz config. However we should still prefer a lower refresh rate if the content doesn't
+    // align well with both
+    const RefreshRate* bestSoFar = *iter;
+    constexpr float MARGIN = 0.05f;
+    float ratio = (*iter)->fps / contentFramerate;
+    if (std::abs(std::round(ratio) - ratio) > MARGIN) {
+        while (iter != mAvailableRefreshRates.cend()) {
+            ratio = (*iter)->fps / contentFramerate;
 
-std::pair<RefreshRateType, const RefreshRate&> RefreshRateConfigs::getCurrentRefreshRate() const {
-    int currentConfig = mCurrentConfig;
-    if (mRefreshRateSwitchingSupported) {
-        for (const auto& [type, refresh] : mRefreshRateMap) {
-            if (refresh.configId == currentConfig) {
-                return {type, refresh};
+            if (std::abs(std::round(ratio) - ratio) <= MARGIN) {
+                bestSoFar = *iter;
+                break;
             }
-        }
-        LOG_ALWAYS_FATAL();
-    }
-    return {RefreshRateType::DEFAULT, mRefreshRates[currentConfig]};
-}
-
-const RefreshRate& RefreshRateConfigs::getRefreshRateFromConfigId(int configId) const {
-    LOG_ALWAYS_FATAL_IF(configId >= mRefreshRates.size());
-    return mRefreshRates[configId];
-}
-
-RefreshRateType RefreshRateConfigs::getRefreshRateTypeFromHwcConfigId(hwc2_config_t hwcId) const {
-    if (!mRefreshRateSwitchingSupported) return RefreshRateType::DEFAULT;
-
-    for (const auto& [type, refreshRate] : mRefreshRateMap) {
-        if (refreshRate.hwcId == hwcId) {
-            return type;
+            ++iter;
         }
     }
 
-    return RefreshRateType::DEFAULT;
+    return *bestSoFar;
 }
 
-void RefreshRateConfigs::setCurrentConfig(int config) {
-    LOG_ALWAYS_FATAL_IF(config >= mRefreshRates.size());
-    mCurrentConfig = config;
+const AllRefreshRatesMapType& RefreshRateConfigs::getAllRefreshRates() const {
+    return mRefreshRates;
+}
+
+const RefreshRate& RefreshRateConfigs::getMinRefreshRateByPolicy() const {
+    std::lock_guard lock(mLock);
+    if (!mRefreshRateSwitching) {
+        return *mCurrentRefreshRate;
+    } else {
+        return *mAvailableRefreshRates.front();
+    }
+}
+
+const RefreshRate& RefreshRateConfigs::getMaxRefreshRateByPolicy() const {
+    std::lock_guard lock(mLock);
+    if (!mRefreshRateSwitching) {
+        return *mCurrentRefreshRate;
+    } else {
+        return *mAvailableRefreshRates.back();
+    }
+}
+
+const RefreshRate& RefreshRateConfigs::getCurrentRefreshRate() const {
+    std::lock_guard lock(mLock);
+    return *mCurrentRefreshRate;
+}
+
+void RefreshRateConfigs::setCurrentConfigId(HwcConfigIndexType configId) {
+    std::lock_guard lock(mLock);
+    mCurrentRefreshRate = &mRefreshRates.at(configId);
 }
 
 RefreshRateConfigs::RefreshRateConfigs(bool refreshRateSwitching,
-                                       const std::vector<InputConfig>& configs, int currentConfig) {
-    init(refreshRateSwitching, configs, currentConfig);
+                                       const std::vector<InputConfig>& configs,
+                                       HwcConfigIndexType currentHwcConfig)
+      : mRefreshRateSwitching(refreshRateSwitching) {
+    init(configs, currentHwcConfig);
 }
 
 RefreshRateConfigs::RefreshRateConfigs(
         bool refreshRateSwitching,
         const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
-        int currentConfig) {
+        HwcConfigIndexType currentConfigId)
+      : mRefreshRateSwitching(refreshRateSwitching) {
     std::vector<InputConfig> inputConfigs;
-    for (const auto& config : configs) {
-        inputConfigs.push_back({config->getId(), config->getVsyncPeriod()});
+    for (auto configId = HwcConfigIndexType(0); configId < HwcConfigIndexType(configs.size());
+         ++configId) {
+        auto configGroup = HwcConfigGroupType(configs[configId.value()]->getConfigGroup());
+        inputConfigs.push_back(
+                {configId, configGroup, configs[configId.value()]->getVsyncPeriod()});
     }
-    init(refreshRateSwitching, inputConfigs, currentConfig);
+    init(inputConfigs, currentConfigId);
 }
 
-void RefreshRateConfigs::init(bool refreshRateSwitching, const std::vector<InputConfig>& configs,
-                              int currentConfig) {
-    mRefreshRateSwitchingSupported = refreshRateSwitching;
+void RefreshRateConfigs::setPolicy(HwcConfigIndexType defaultConfigId, float minRefreshRate,
+                                   float maxRefreshRate) {
+    std::lock_guard lock(mLock);
+    mCurrentGroupId = mRefreshRates.at(defaultConfigId).configGroup;
+    mMinRefreshRateFps = minRefreshRate;
+    mMaxRefreshRateFps = maxRefreshRate;
+    constructAvailableRefreshRates();
+}
+
+void RefreshRateConfigs::getSortedRefreshRateList(
+        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("getSortedRefreshRateList: config %d added to list policy",
+                  refreshRate.configId.value());
+            outRefreshRates->push_back(&refreshRate);
+        }
+    }
+
+    std::sort(outRefreshRates->begin(), outRefreshRates->end(),
+              [](const auto refreshRate1, const auto refreshRate2) {
+                  return refreshRate1->vsyncPeriod > refreshRate2->vsyncPeriod;
+              });
+}
+
+void RefreshRateConfigs::constructAvailableRefreshRates() {
+    // Filter configs based on current policy and sort based on vsync period
+    ALOGV("constructRefreshRateMap: group %d min %.2f max %.2f", mCurrentGroupId.value(),
+          mMinRefreshRateFps, mMaxRefreshRateFps);
+    getSortedRefreshRateList(
+            [this](const RefreshRate& refreshRate) REQUIRES(mLock) {
+                return refreshRate.configGroup == mCurrentGroupId &&
+                        refreshRate.fps >= mMinRefreshRateFps &&
+                        refreshRate.fps <= mMaxRefreshRateFps;
+            },
+            &mAvailableRefreshRates);
+}
+
+// NO_THREAD_SAFETY_ANALYSIS since this is called from the constructor
+void RefreshRateConfigs::init(const std::vector<InputConfig>& configs,
+                              HwcConfigIndexType currentHwcConfig) NO_THREAD_SAFETY_ANALYSIS {
     LOG_ALWAYS_FATAL_IF(configs.empty());
-    LOG_ALWAYS_FATAL_IF(currentConfig >= configs.size());
-    mCurrentConfig = currentConfig;
+    LOG_ALWAYS_FATAL_IF(currentHwcConfig.value() >= configs.size());
 
-    auto buildRefreshRate = [&](int configId) -> RefreshRate {
-        const nsecs_t vsyncPeriod = configs[configId].vsyncPeriod;
-        const float fps = 1e9 / vsyncPeriod;
-        return {configId, base::StringPrintf("%2.ffps", fps), static_cast<uint32_t>(fps),
-                vsyncPeriod, configs[configId].hwcId};
+    auto buildRefreshRate = [&](InputConfig config) -> RefreshRate {
+        const float fps = 1e9f / config.vsyncPeriod;
+        return RefreshRate(config.configId, config.vsyncPeriod, config.configGroup,
+                           base::StringPrintf("%2.ffps", fps), fps);
     };
 
-    for (int i = 0; i < configs.size(); ++i) {
-        mRefreshRates.push_back(buildRefreshRate(i));
+    for (const auto& config : configs) {
+        mRefreshRates.emplace(config.configId, buildRefreshRate(config));
+        if (config.configId == currentHwcConfig) {
+            mCurrentRefreshRate = &mRefreshRates.at(config.configId);
+            mCurrentGroupId = config.configGroup;
+        }
     }
 
-    if (!mRefreshRateSwitchingSupported) return;
-
-    auto findDefaultAndPerfConfigs = [&]() -> std::optional<std::pair<int, int>> {
-        if (configs.size() < 2) {
-            return {};
-        }
-
-        std::vector<const RefreshRate*> sortedRefreshRates;
-        for (const auto& refreshRate : mRefreshRates) {
-            sortedRefreshRates.push_back(&refreshRate);
-        }
-        std::sort(sortedRefreshRates.begin(), sortedRefreshRates.end(),
-                  [](const RefreshRate* refreshRate1, const RefreshRate* refreshRate2) {
-                      return refreshRate1->vsyncPeriod > refreshRate2->vsyncPeriod;
-                  });
-
-        // When the configs are ordered by the resync rate, we assume that
-        // the first one is DEFAULT and the second one is PERFORMANCE,
-        // i.e. the higher rate.
-        if (sortedRefreshRates[0]->vsyncPeriod == 0 || sortedRefreshRates[1]->vsyncPeriod == 0) {
-            return {};
-        }
-
-        return std::pair<int, int>(sortedRefreshRates[0]->configId,
-                                   sortedRefreshRates[1]->configId);
-    };
-
-    auto defaultAndPerfConfigs = findDefaultAndPerfConfigs();
-    if (!defaultAndPerfConfigs) {
-        mRefreshRateSwitchingSupported = false;
-        return;
-    }
-
-    mRefreshRateMap[RefreshRateType::DEFAULT] = mRefreshRates[defaultAndPerfConfigs->first];
-    mRefreshRateMap[RefreshRateType::PERFORMANCE] = mRefreshRates[defaultAndPerfConfigs->second];
+    std::vector<const RefreshRate*> sortedConfigs;
+    getSortedRefreshRateList([](const RefreshRate&) { return true; }, &sortedConfigs);
+    mMinSupportedRefreshRate = sortedConfigs.front();
+    mMaxSupportedRefreshRate = sortedConfigs.back();
+    constructAvailableRefreshRates();
 }
 
-} // namespace android::scheduler
\ No newline at end of file
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 90bba24..fb14dc7 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -23,7 +23,9 @@
 #include <type_traits>
 
 #include "DisplayHardware/HWComposer.h"
+#include "HwcStrongTypes.h"
 #include "Scheduler/SchedulerUtils.h"
+#include "Scheduler/StrongTyping.h"
 
 namespace android::scheduler {
 
@@ -41,71 +43,123 @@
  */
 class RefreshRateConfigs {
 public:
-    // Enum to indicate which vsync rate to run at. Default is the old 60Hz, and performance
-    // is the new 90Hz. Eventually we want to have a way for vendors to map these in the configs.
-    enum class RefreshRateType { DEFAULT, PERFORMANCE };
-
     struct RefreshRate {
+        RefreshRate(HwcConfigIndexType configId, nsecs_t vsyncPeriod,
+                    HwcConfigGroupType configGroup, std::string name, float fps)
+              : configId(configId),
+                vsyncPeriod(vsyncPeriod),
+                configGroup(configGroup),
+                name(std::move(name)),
+                fps(fps) {}
         // This config ID corresponds to the position of the config in the vector that is stored
         // on the device.
-        int configId;
-        // Human readable name of the refresh rate.
-        std::string name;
-        // Refresh rate in frames per second, rounded to the nearest integer.
-        uint32_t fps = 0;
+        const HwcConfigIndexType configId;
         // Vsync period in nanoseconds.
-        nsecs_t vsyncPeriod;
-        // Hwc config Id (returned from HWC2::Display::Config::getId())
-        hwc2_config_t hwcId;
+        const nsecs_t vsyncPeriod;
+        // This configGroup for the config.
+        const HwcConfigGroupType configGroup;
+        // Human readable name of the refresh rate.
+        const std::string name;
+        // Refresh rate in frames per second
+        const float fps = 0;
+
+        bool operator!=(const RefreshRate& other) const {
+            return configId != other.configId || vsyncPeriod != other.vsyncPeriod ||
+                    configGroup != other.configGroup;
+        }
+
+        bool operator==(const RefreshRate& other) const { return !(*this != other); }
     };
 
+    using AllRefreshRatesMapType = std::unordered_map<HwcConfigIndexType, const RefreshRate>;
+
+    // Sets the current policy to choose refresh rates.
+    void setPolicy(HwcConfigIndexType defaultConfigId, float minRefreshRate, float maxRefreshRate)
+            EXCLUDES(mLock);
+
     // Returns true if this device is doing refresh rate switching. This won't change at runtime.
-    bool refreshRateSwitchingSupported() const { return mRefreshRateSwitchingSupported; }
+    bool refreshRateSwitchingSupported() const { return mRefreshRateSwitching; }
 
-    // Returns the refresh rate map. This map won't be modified at runtime, so it's safe to access
-    // from multiple threads. This can only be called if refreshRateSwitching() returns true.
-    // TODO(b/122916473): Get this information from configs prepared by vendors, instead of
-    // baking them in.
-    const std::map<RefreshRateType, RefreshRate>& getRefreshRateMap() const;
+    // Returns all available refresh rates according to the current policy.
+    const RefreshRate& getRefreshRateForContent(float contentFramerate) const EXCLUDES(mLock);
 
-    const RefreshRate& getRefreshRateFromType(RefreshRateType type) const;
+    // Returns all the refresh rates supported by the device. This won't change at runtime.
+    const AllRefreshRatesMapType& getAllRefreshRates() const EXCLUDES(mLock);
 
-    std::pair<RefreshRateType, const RefreshRate&> getCurrentRefreshRate() const;
+    // Returns the lowest refresh rate supported by the device. This won't change at runtime.
+    const RefreshRate& getMinRefreshRate() const { return *mMinSupportedRefreshRate; }
 
-    const RefreshRate& getRefreshRateFromConfigId(int configId) const;
+    // Returns the lowest refresh rate according to the current policy. May change in runtime.
+    const RefreshRate& getMinRefreshRateByPolicy() const EXCLUDES(mLock);
 
-    RefreshRateType getRefreshRateTypeFromHwcConfigId(hwc2_config_t hwcId) const;
+    // Returns the highest refresh rate supported by the device. This won't change at runtime.
+    const RefreshRate& getMaxRefreshRate() const { return *mMaxSupportedRefreshRate; }
 
-    void setCurrentConfig(int config);
+    // Returns the highest refresh rate according to the current policy. May change in runtime.
+    const RefreshRate& getMaxRefreshRateByPolicy() const EXCLUDES(mLock);
+
+    // Returns the current refresh rate
+    const RefreshRate& getCurrentRefreshRate() const EXCLUDES(mLock);
+
+    // Returns the refresh rate that corresponds to a HwcConfigIndexType. This won't change at
+    // runtime.
+    const RefreshRate& getRefreshRateFromConfigId(HwcConfigIndexType configId) const {
+        return mRefreshRates.at(configId);
+    };
+
+    // Stores the current configId the device operates at
+    void setCurrentConfigId(HwcConfigIndexType configId) EXCLUDES(mLock);
 
     struct InputConfig {
-        hwc2_config_t hwcId = 0;
+        HwcConfigIndexType configId = HwcConfigIndexType(0);
+        HwcConfigGroupType configGroup = HwcConfigGroupType(0);
         nsecs_t vsyncPeriod = 0;
     };
 
     RefreshRateConfigs(bool refreshRateSwitching, const std::vector<InputConfig>& configs,
-                       int currentConfig);
-
+                       HwcConfigIndexType currentHwcConfig);
     RefreshRateConfigs(bool refreshRateSwitching,
                        const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
-                       int currentConfig);
+                       HwcConfigIndexType currentConfigId);
 
 private:
-    void init(bool refreshRateSwitching, const std::vector<InputConfig>& configs,
-              int currentConfig);
-    // Whether this device is doing refresh rate switching or not. This must not change after this
-    // object is initialized.
-    bool mRefreshRateSwitchingSupported;
+    void init(const std::vector<InputConfig>& configs, HwcConfigIndexType currentHwcConfig);
+
+    void constructAvailableRefreshRates() REQUIRES(mLock);
+
+    void getSortedRefreshRateList(
+            const std::function<bool(const RefreshRate&)>& shouldAddRefreshRate,
+            std::vector<const RefreshRate*>* outRefreshRates);
+
     // The list of refresh rates, indexed by display config ID. This must not change after this
     // object is initialized.
-    std::vector<RefreshRate> mRefreshRates;
-    // The mapping of refresh rate type to RefreshRate. This must not change after this object is
-    // initialized.
-    std::map<RefreshRateType, RefreshRate> mRefreshRateMap;
-    // The ID of the current config. 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, so it's
-    // atomic.
-    std::atomic<int> mCurrentConfig;
+    AllRefreshRatesMapType mRefreshRates;
+
+    // The list of refresh rates which are available in the current policy, ordered by vsyncPeriod
+    // (the first element is the lowest refresh rate)
+    std::vector<const RefreshRate*> mAvailableRefreshRates GUARDED_BY(mLock);
+
+    // The current config. 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 current config group. 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.
+    HwcConfigGroupType mCurrentGroupId GUARDED_BY(mLock);
+
+    // The min and max FPS allowed by the policy. This will change at runtime and set by
+    // SurfaceFlinger on the main thread.
+    float mMinRefreshRateFps GUARDED_BY(mLock) = 0;
+    float mMaxRefreshRateFps GUARDED_BY(mLock) = std::numeric_limits<float>::max();
+
+    // The min and max refresh rates supported by the device.
+    // This will not change at runtime.
+    const RefreshRate* mMinSupportedRefreshRate;
+    const RefreshRate* mMaxSupportedRefreshRate;
+
+    const bool mRefreshRateSwitching;
+
+    mutable std::mutex mLock;
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateStats.h b/services/surfaceflinger/Scheduler/RefreshRateStats.h
index 8afc93e..a384dbe 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateStats.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateStats.h
@@ -25,8 +25,7 @@
 #include "android-base/stringprintf.h"
 #include "utils/Timers.h"
 
-namespace android {
-namespace scheduler {
+namespace android::scheduler {
 
 /**
  * Class to encapsulate statistics about refresh rates that the display is using. When the power
@@ -42,10 +41,10 @@
 
 public:
     RefreshRateStats(const RefreshRateConfigs& refreshRateConfigs, TimeStats& timeStats,
-                     int currentConfigMode, int currentPowerMode)
+                     HwcConfigIndexType currentConfigId, int currentPowerMode)
           : mRefreshRateConfigs(refreshRateConfigs),
             mTimeStats(timeStats),
-            mCurrentConfigMode(currentConfigMode),
+            mCurrentConfigMode(currentConfigId),
             mCurrentPowerMode(currentPowerMode) {}
 
     // Sets power mode.
@@ -59,12 +58,12 @@
 
     // Sets config mode. If the mode has changed, it records how much time was spent in the previous
     // mode.
-    void setConfigMode(int mode) {
-        if (mCurrentConfigMode == mode) {
+    void setConfigMode(HwcConfigIndexType configId) {
+        if (mCurrentConfigMode == configId) {
             return;
         }
         flushTime();
-        mCurrentConfigMode = mode;
+        mCurrentConfigMode = configId;
     }
 
     // Returns a map between human readable refresh rate and number of seconds the device spent in
@@ -78,11 +77,11 @@
         std::unordered_map<std::string, int64_t> totalTime;
         // Multiple configs may map to the same name, e.g. "60fps". Add the
         // times for such configs together.
-        for (const auto& [config, time] : mConfigModesTotalTime) {
-            totalTime[mRefreshRateConfigs.getRefreshRateFromConfigId(config).name] = 0;
+        for (const auto& [configId, time] : mConfigModesTotalTime) {
+            totalTime[mRefreshRateConfigs.getRefreshRateFromConfigId(configId).name] = 0;
         }
-        for (const auto& [config, time] : mConfigModesTotalTime) {
-            totalTime[mRefreshRateConfigs.getRefreshRateFromConfigId(config).name] += time;
+        for (const auto& [configId, time] : mConfigModesTotalTime) {
+            totalTime[mRefreshRateConfigs.getRefreshRateFromConfigId(configId).name] += time;
         }
         totalTime["ScreenOff"] = mScreenOffTime;
         return totalTime;
@@ -139,14 +138,14 @@
     // Aggregate refresh rate statistics for telemetry.
     TimeStats& mTimeStats;
 
-    int mCurrentConfigMode;
+    HwcConfigIndexType mCurrentConfigMode;
     int32_t mCurrentPowerMode;
 
-    std::unordered_map<int /* config */, int64_t /* duration in ms */> mConfigModesTotalTime;
+    std::unordered_map<HwcConfigIndexType /* configId */, int64_t /* duration in ms */>
+            mConfigModesTotalTime;
     int64_t mScreenOffTime = 0;
 
     nsecs_t mPreviousRecordedTime = systemTime();
 };
 
-} // namespace scheduler
-} // namespace android
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 55fd603..1d50fe1 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -182,7 +182,7 @@
 }
 
 void Scheduler::onConfigChanged(ConnectionHandle handle, PhysicalDisplayId displayId,
-                                int32_t configId) {
+                                HwcConfigIndexType configId) {
     RETURN_IF_INVALID_HANDLE(handle);
     mConnections[handle].thread->onConfigChanged(displayId, configId);
 }
@@ -280,8 +280,7 @@
     const nsecs_t last = mLastResyncTime.exchange(now);
 
     if (now - last > kIgnoreDelay) {
-        resyncToHardwareVsync(false,
-                              mRefreshRateConfigs.getCurrentRefreshRate().second.vsyncPeriod);
+        resyncToHardwareVsync(false, mRefreshRateConfigs.getCurrentRefreshRate().vsyncPeriod);
     }
 }
 
@@ -332,53 +331,49 @@
 void Scheduler::registerLayer(Layer* layer) {
     if (!mLayerHistory) return;
 
-    const auto type = layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER
-            ? RefreshRateType::DEFAULT
-            : RefreshRateType::PERFORMANCE;
-
-    const auto lowFps = mRefreshRateConfigs.getRefreshRateFromType(RefreshRateType::DEFAULT).fps;
-    const auto highFps = mRefreshRateConfigs.getRefreshRateFromType(type).fps;
+    const auto lowFps = mRefreshRateConfigs.getMinRefreshRate().fps;
+    const auto highFps = layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER
+            ? lowFps
+            : mRefreshRateConfigs.getMaxRefreshRate().fps;
 
     mLayerHistory->registerLayer(layer, lowFps, highFps);
 }
 
-void Scheduler::recordLayerHistory(Layer* layer, nsecs_t presentTime, bool isHDR) {
+void Scheduler::recordLayerHistory(Layer* layer, nsecs_t presentTime) {
     if (mLayerHistory) {
-        mLayerHistory->record(layer, presentTime, isHDR, systemTime());
+        mLayerHistory->record(layer, presentTime, systemTime());
     }
 }
 
 void Scheduler::chooseRefreshRateForContent() {
     if (!mLayerHistory) return;
 
-    auto [refreshRate, isHDR] = mLayerHistory->summarize(systemTime());
+    auto [refreshRate] = mLayerHistory->summarize(systemTime());
     const uint32_t refreshRateRound = std::round(refreshRate);
-    RefreshRateType newRefreshRateType;
+    HwcConfigIndexType newConfigId;
     {
         std::lock_guard<std::mutex> lock(mFeatureStateLock);
-        if (mFeatures.contentRefreshRate == refreshRateRound && mFeatures.isHDRContent == isHDR) {
+        if (mFeatures.contentRefreshRate == refreshRateRound) {
             return;
         }
         mFeatures.contentRefreshRate = refreshRateRound;
         ATRACE_INT("ContentFPS", refreshRateRound);
 
-        mFeatures.isHDRContent = isHDR;
-        ATRACE_INT("ContentHDR", isHDR);
-
         mFeatures.contentDetection =
                 refreshRateRound > 0 ? ContentDetectionState::On : ContentDetectionState::Off;
-        newRefreshRateType = calculateRefreshRateType();
-        if (mFeatures.refreshRateType == newRefreshRateType) {
+        newConfigId = calculateRefreshRateType();
+        if (mFeatures.configId == newConfigId) {
             return;
         }
-        mFeatures.refreshRateType = newRefreshRateType;
-    }
-    changeRefreshRate(newRefreshRateType, ConfigEvent::Changed);
+        mFeatures.configId = newConfigId;
+    };
+    auto newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
+    changeRefreshRate(newRefreshRate, ConfigEvent::Changed);
 }
 
-void Scheduler::setChangeRefreshRateCallback(ChangeRefreshRateCallback&& callback) {
+void Scheduler::setSchedulerCallback(android::Scheduler::ISchedulerCallback* callback) {
     std::lock_guard<std::mutex> lock(mCallbackLock);
-    mChangeRefreshRateCallback = std::move(callback);
+    mSchedulerCallback = callback;
 }
 
 void Scheduler::resetIdleTimer() {
@@ -423,13 +418,16 @@
 void Scheduler::kernelIdleTimerCallback(TimerState state) {
     ATRACE_INT("ExpiredKernelIdleTimer", static_cast<int>(state));
 
+    // TODO(145561154): cleanup the kernel idle timer implementation and the refresh rate
+    // magic number
     const auto refreshRate = mRefreshRateConfigs.getCurrentRefreshRate();
-    if (state == TimerState::Reset && refreshRate.first == RefreshRateType::PERFORMANCE) {
+    constexpr float FPS_THRESHOLD_FOR_KERNEL_TIMER = 65.0f;
+    if (state == TimerState::Reset && refreshRate.fps > 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.second.vsyncPeriod);
-    } else if (state == TimerState::Expired && refreshRate.first != RefreshRateType::PERFORMANCE) {
+        resyncToHardwareVsync(true /* makeAvailable */, refreshRate.vsyncPeriod);
+    } else if (state == TimerState::Expired && refreshRate.fps <= 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 DispSync model anyway.
@@ -471,96 +469,67 @@
 template <class T>
 void Scheduler::handleTimerStateChanged(T* currentState, T newState, bool eventOnContentDetection) {
     ConfigEvent event = ConfigEvent::None;
-    RefreshRateType newRefreshRateType;
+    HwcConfigIndexType newConfigId;
     {
         std::lock_guard<std::mutex> lock(mFeatureStateLock);
         if (*currentState == newState) {
             return;
         }
         *currentState = newState;
-        newRefreshRateType = calculateRefreshRateType();
-        if (mFeatures.refreshRateType == newRefreshRateType) {
+        newConfigId = calculateRefreshRateType();
+        if (mFeatures.configId == newConfigId) {
             return;
         }
-        mFeatures.refreshRateType = newRefreshRateType;
+        mFeatures.configId = newConfigId;
         if (eventOnContentDetection && mFeatures.contentDetection == ContentDetectionState::On) {
             event = ConfigEvent::Changed;
         }
     }
-    changeRefreshRate(newRefreshRateType, event);
+    const RefreshRate& newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
+    changeRefreshRate(newRefreshRate, event);
 }
 
-Scheduler::RefreshRateType Scheduler::calculateRefreshRateType() {
+HwcConfigIndexType Scheduler::calculateRefreshRateType() {
     if (!mRefreshRateConfigs.refreshRateSwitchingSupported()) {
-        return RefreshRateType::DEFAULT;
-    }
-
-    // HDR content is not supported on PERFORMANCE mode
-    if (mForceHDRContentToDefaultRefreshRate && mFeatures.isHDRContent) {
-        return RefreshRateType::DEFAULT;
+        return mRefreshRateConfigs.getCurrentRefreshRate().configId;
     }
 
     // If Display Power is not in normal operation we want to be in performance mode.
     // When coming back to normal mode, a grace period is given with DisplayPowerTimer
     if (!mFeatures.isDisplayPowerStateNormal || mFeatures.displayPowerTimer == TimerState::Reset) {
-        return RefreshRateType::PERFORMANCE;
+        return mRefreshRateConfigs.getMaxRefreshRateByPolicy().configId;
     }
 
     // As long as touch is active we want to be in performance mode
     if (mFeatures.touch == TouchState::Active) {
-        return RefreshRateType::PERFORMANCE;
+        return mRefreshRateConfigs.getMaxRefreshRateByPolicy().configId;
     }
 
     // If timer has expired as it means there is no new content on the screen
     if (mFeatures.idleTimer == TimerState::Expired) {
-        return RefreshRateType::DEFAULT;
+        return mRefreshRateConfigs.getMinRefreshRateByPolicy().configId;
     }
 
     // If content detection is off we choose performance as we don't know the content fps
     if (mFeatures.contentDetection == ContentDetectionState::Off) {
-        return RefreshRateType::PERFORMANCE;
+        return mRefreshRateConfigs.getMaxRefreshRateByPolicy().configId;
     }
 
     // Content detection is on, find the appropriate refresh rate with minimal error
-    // TODO(b/139751853): Scan allowed refresh rates only (SurfaceFlinger::mAllowedDisplayConfigs)
-    const float rate = static_cast<float>(mFeatures.contentRefreshRate);
-    auto iter = min_element(mRefreshRateConfigs.getRefreshRateMap().cbegin(),
-                            mRefreshRateConfigs.getRefreshRateMap().cend(),
-                            [rate](const auto& lhs, const auto& rhs) -> bool {
-                                return std::abs(lhs.second.fps - rate) <
-                                        std::abs(rhs.second.fps - rate);
-                            });
-    RefreshRateType currRefreshRateType = iter->first;
-
-    // Some content aligns better on higher refresh rate. For example for 45fps we should choose
-    // 90Hz config. However we should still prefer a lower refresh rate if the content doesn't
-    // align well with both
-    constexpr float MARGIN = 0.05f;
-    float ratio = mRefreshRateConfigs.getRefreshRateFromType(currRefreshRateType).fps / rate;
-    if (std::abs(std::round(ratio) - ratio) > MARGIN) {
-        while (iter != mRefreshRateConfigs.getRefreshRateMap().cend()) {
-            ratio = iter->second.fps / rate;
-
-            if (std::abs(std::round(ratio) - ratio) <= MARGIN) {
-                currRefreshRateType = iter->first;
-                break;
-            }
-            ++iter;
-        }
-    }
-
-    return currRefreshRateType;
+    return mRefreshRateConfigs
+            .getRefreshRateForContent(static_cast<float>(mFeatures.contentRefreshRate))
+            .configId;
 }
 
-Scheduler::RefreshRateType Scheduler::getPreferredRefreshRateType() {
+std::optional<HwcConfigIndexType> Scheduler::getPreferredConfigId() {
     std::lock_guard<std::mutex> lock(mFeatureStateLock);
-    return mFeatures.refreshRateType;
+    return mFeatures.configId;
 }
 
-void Scheduler::changeRefreshRate(RefreshRateType refreshRateType, ConfigEvent configEvent) {
+void Scheduler::changeRefreshRate(const RefreshRate& refreshRate, ConfigEvent configEvent) {
     std::lock_guard<std::mutex> lock(mCallbackLock);
-    if (mChangeRefreshRateCallback) {
-        mChangeRefreshRateCallback(refreshRateType, configEvent);
+    if (mSchedulerCallback) {
+        mSchedulerCallback->changeRefreshRate(refreshRate, configEvent);
     }
 }
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 346896c..04a8390 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -34,6 +34,8 @@
 
 namespace android {
 
+using namespace std::chrono_literals;
+
 class DispSync;
 class FenceTime;
 class InjectVSyncSource;
@@ -41,10 +43,14 @@
 
 class Scheduler {
 public:
-    using RefreshRateType = scheduler::RefreshRateConfigs::RefreshRateType;
+    using RefreshRate = scheduler::RefreshRateConfigs::RefreshRate;
     using ConfigEvent = scheduler::RefreshRateConfigEvent;
 
-    using ChangeRefreshRateCallback = std::function<void(RefreshRateType, ConfigEvent)>;
+    class ISchedulerCallback {
+    public:
+        virtual ~ISchedulerCallback() = default;
+        virtual void changeRefreshRate(const RefreshRate&, ConfigEvent) = 0;
+    };
 
     // Indicates whether to start the transaction early, or at vsync time.
     enum class TransactionStart { EARLY, NORMAL };
@@ -67,7 +73,7 @@
     sp<EventThreadConnection> getEventConnection(ConnectionHandle);
 
     void onHotplugReceived(ConnectionHandle, PhysicalDisplayId, bool connected);
-    void onConfigChanged(ConnectionHandle, PhysicalDisplayId, int32_t configId);
+    void onConfigChanged(ConnectionHandle, PhysicalDisplayId, HwcConfigIndexType configId);
 
     void onScreenAcquired(ConnectionHandle);
     void onScreenReleased(ConnectionHandle);
@@ -103,13 +109,13 @@
 
     // Layers are registered on creation, and unregistered when the weak reference expires.
     void registerLayer(Layer*);
-    void recordLayerHistory(Layer*, nsecs_t presentTime, bool isHDR);
+    void recordLayerHistory(Layer*, nsecs_t presentTime);
 
     // Detects content using layer history, and selects a matching refresh rate.
     void chooseRefreshRateForContent();
 
-    // Called by Scheduler to change refresh rate.
-    void setChangeRefreshRateCallback(ChangeRefreshRateCallback&&);
+    // Called by Scheduler to control SurfaceFlinger operations.
+    void setSchedulerCallback(ISchedulerCallback*);
 
     bool isIdleTimerEnabled() const { return mIdleTimer.has_value(); }
     void resetIdleTimer();
@@ -122,8 +128,8 @@
     void dump(std::string&) const;
     void dump(ConnectionHandle, std::string&) const;
 
-    // Get the appropriate refresh type for current conditions.
-    RefreshRateType getPreferredRefreshRateType();
+    // Get the appropriate refresh for current conditions.
+    std::optional<HwcConfigIndexType> getPreferredConfigId();
 
 private:
     friend class TestableScheduler;
@@ -158,9 +164,9 @@
 
     void setVsyncPeriod(nsecs_t period);
 
-    RefreshRateType calculateRefreshRateType() REQUIRES(mFeatureStateLock);
+    HwcConfigIndexType calculateRefreshRateType() REQUIRES(mFeatureStateLock);
     // Acquires a lock and calls the ChangeRefreshRateCallback with given parameters.
-    void changeRefreshRate(RefreshRateType, ConfigEvent);
+    void changeRefreshRate(const RefreshRate&, ConfigEvent);
 
     // Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection.
     struct Connection {
@@ -198,7 +204,7 @@
     std::optional<scheduler::OneShotTimer> mDisplayPowerTimer;
 
     std::mutex mCallbackLock;
-    ChangeRefreshRateCallback mChangeRefreshRateCallback GUARDED_BY(mCallbackLock);
+    ISchedulerCallback* mSchedulerCallback GUARDED_BY(mCallbackLock) = nullptr;
 
     // In order to make sure that the features don't override themselves, we need a state machine
     // to keep track which feature requested the config change.
@@ -210,17 +216,13 @@
         TouchState touch = TouchState::Inactive;
         TimerState displayPowerTimer = TimerState::Expired;
 
-        RefreshRateType refreshRateType = RefreshRateType::DEFAULT;
+        std::optional<HwcConfigIndexType> configId;
         uint32_t contentRefreshRate = 0;
 
-        bool isHDRContent = false;
         bool isDisplayPowerStateNormal = true;
     } mFeatures GUARDED_BY(mFeatureStateLock);
 
     const scheduler::RefreshRateConfigs& mRefreshRateConfigs;
-
-    // Global config to force HDR content to work on DEFAULT refreshRate
-    static constexpr bool mForceHDRContentToDefaultRefreshRate = false;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/StrongTyping.h b/services/surfaceflinger/Scheduler/StrongTyping.h
index 02db022..e8ca0ba 100644
--- a/services/surfaceflinger/Scheduler/StrongTyping.h
+++ b/services/surfaceflinger/Scheduler/StrongTyping.h
@@ -51,13 +51,22 @@
     inline bool operator>(T const& other) const { return !(*this < other || *this == other); }
 };
 
+template <typename T>
+struct Hash : Ability<T, Hash> {
+    [[nodiscard]] std::size_t hash() const {
+        return std::hash<typename std::remove_const<
+                typename std::remove_reference<decltype(this->base().value())>::type>::type>{}(
+                this->base().value());
+    }
+};
+
 template <typename T, typename W, template <typename> class... Ability>
 struct StrongTyping : Ability<StrongTyping<T, W, Ability...>>... {
     StrongTyping() : mValue(0) {}
     explicit StrongTyping(T const& value) : mValue(value) {}
     StrongTyping(StrongTyping const&) = default;
     StrongTyping& operator=(StrongTyping const&) = default;
-    inline operator T() const { return mValue; }
+    explicit inline operator T() const { return mValue; }
     T const& value() const { return mValue; }
     T& value() { return mValue; }
 
@@ -65,3 +74,12 @@
     T mValue;
 };
 } // namespace android
+
+namespace std {
+template <typename T, typename W, template <typename> class... Ability>
+struct hash<android::StrongTyping<T, W, Ability...>> {
+    std::size_t operator()(android::StrongTyping<T, W, Ability...> const& k) const {
+        return k.hash();
+    }
+};
+} // namespace std
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatch.h b/services/surfaceflinger/Scheduler/VSyncDispatch.h
index 4a4bef8..e001080 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatch.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatch.h
@@ -34,7 +34,7 @@
  */
 class VSyncDispatch {
 public:
-    using CallbackToken = StrongTyping<size_t, class CallbackTokenTag, Compare>;
+    using CallbackToken = StrongTyping<size_t, class CallbackTokenTag, Compare, Hash>;
 
     virtual ~VSyncDispatch();
 
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
index f058099..0e12e7f 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
@@ -106,7 +106,8 @@
     VSyncDispatchTimerQueue(VSyncDispatchTimerQueue const&) = delete;
     VSyncDispatchTimerQueue& operator=(VSyncDispatchTimerQueue const&) = delete;
 
-    using CallbackMap = std::unordered_map<size_t, std::shared_ptr<VSyncDispatchTimerQueueEntry>>;
+    using CallbackMap =
+            std::unordered_map<CallbackToken, std::shared_ptr<VSyncDispatchTimerQueueEntry>>;
 
     void timerCallback();
     void setTimer(nsecs_t, nsecs_t) REQUIRES(mMutex);
diff --git a/services/surfaceflinger/Scheduler/VSyncModulator.cpp b/services/surfaceflinger/Scheduler/VSyncModulator.cpp
index 27fd76c..8de35b1 100644
--- a/services/surfaceflinger/Scheduler/VSyncModulator.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncModulator.cpp
@@ -134,18 +134,13 @@
         return;
     }
 
-    const bool isDefault = mOffsets.fpsMode == RefreshRateType::DEFAULT;
-    const bool isPerformance = mOffsets.fpsMode == RefreshRateType::PERFORMANCE;
     const bool isEarly = &offsets == &mOffsetsConfig.early;
     const bool isEarlyGl = &offsets == &mOffsetsConfig.earlyGl;
     const bool isLate = &offsets == &mOffsetsConfig.late;
 
-    ATRACE_INT("Vsync-EarlyOffsetsOn", isDefault && isEarly);
-    ATRACE_INT("Vsync-EarlyGLOffsetsOn", isDefault && isEarlyGl);
-    ATRACE_INT("Vsync-LateOffsetsOn", isDefault && isLate);
-    ATRACE_INT("Vsync-HighFpsEarlyOffsetsOn", isPerformance && isEarly);
-    ATRACE_INT("Vsync-HighFpsEarlyGLOffsetsOn", isPerformance && isEarlyGl);
-    ATRACE_INT("Vsync-HighFpsLateOffsetsOn", isPerformance && isLate);
+    ATRACE_INT("Vsync-EarlyOffsetsOn", isEarly);
+    ATRACE_INT("Vsync-EarlyGLOffsetsOn", isEarlyGl);
+    ATRACE_INT("Vsync-LateOffsetsOn", isLate);
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncModulator.h b/services/surfaceflinger/Scheduler/VSyncModulator.h
index 727cef2..63c0feb 100644
--- a/services/surfaceflinger/Scheduler/VSyncModulator.h
+++ b/services/surfaceflinger/Scheduler/VSyncModulator.h
@@ -37,13 +37,10 @@
     // switch in and out of gl composition.
     static constexpr int MIN_EARLY_GL_FRAME_COUNT_TRANSACTION = 2;
 
-    using RefreshRateType = RefreshRateConfigs::RefreshRateType;
-
 public:
     // Wrapper for a collection of surfaceflinger/app offsets for a particular
     // configuration.
     struct Offsets {
-        RefreshRateType fpsMode;
         nsecs_t sf;
         nsecs_t app;
     };