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/BufferQueueLayer.cpp b/services/surfaceflinger/BufferQueueLayer.cpp
index d51d34b..8957e90 100644
--- a/services/surfaceflinger/BufferQueueLayer.cpp
+++ b/services/surfaceflinger/BufferQueueLayer.cpp
@@ -399,8 +399,7 @@
     // Add this buffer from our internal queue tracker
     { // Autolock scope
         const nsecs_t presentTime = item.mIsAutoTimestamp ? 0 : item.mTimestamp;
-        const bool isHDR = item.mHdrMetadata.validTypes != 0;
-        mFlinger->mScheduler->recordLayerHistory(this, presentTime, isHDR);
+        mFlinger->mScheduler->recordLayerHistory(this, presentTime);
 
         Mutex::Autolock lock(mQueueItemLock);
         // Reset the frame number tracker when we receive the first buffer after
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index d68fe8e..1e471e5 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -247,8 +247,7 @@
                                            FrameTracer::FrameEvent::POST);
     mCurrentState.desiredPresentTime = desiredPresentTime;
 
-    const bool isHDR = mCurrentState.hdrMetadata.validTypes != 0;
-    mFlinger->mScheduler->recordLayerHistory(this, desiredPresentTime, isHDR);
+    mFlinger->mScheduler->recordLayerHistory(this, desiredPresentTime);
 
     return true;
 }
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 89123df..84ec597 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -132,11 +132,11 @@
 }
 
 // ----------------------------------------------------------------------------
-void DisplayDevice::setActiveConfig(int mode) {
+void DisplayDevice::setActiveConfig(HwcConfigIndexType mode) {
     mActiveConfig = mode;
 }
 
-int DisplayDevice::getActiveConfig()  const {
+HwcConfigIndexType DisplayDevice::getActiveConfig() const {
     return mActiveConfig;
 }
 
@@ -285,7 +285,7 @@
 
     result.append("   ");
     StringAppendF(&result, "powerMode=%d, ", mPowerMode);
-    StringAppendF(&result, "activeConfig=%d, ", mActiveConfig);
+    StringAppendF(&result, "activeConfig=%d, ", mActiveConfig.value());
     getCompositionDisplay()->dump(result);
 }
 
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index ce4e1e6..2d0875b 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -43,6 +43,7 @@
 #include "DisplayHardware/DisplayIdentification.h"
 #include "DisplayHardware/PowerAdvisor.h"
 #include "RenderArea.h"
+#include "Scheduler/HwcStrongTypes.h"
 
 namespace android {
 
@@ -141,8 +142,8 @@
     /* ------------------------------------------------------------------------
      * Display active config management.
      */
-    int getActiveConfig() const;
-    void setActiveConfig(int mode);
+    HwcConfigIndexType getActiveConfig() const;
+    void setActiveConfig(HwcConfigIndexType mode);
 
     // release HWC resources (if any) for removable displays
     void disconnect();
@@ -186,7 +187,7 @@
     // Current power mode
     int mPowerMode;
     // Current active config
-    int mActiveConfig;
+    HwcConfigIndexType mActiveConfig;
 
     // TODO(b/74619554): Remove special cases for primary display.
     const bool mIsPrimary;
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index 976fedb..38a80a7 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -20,8 +20,6 @@
 
 namespace android {
 
-using RefreshRateType = scheduler::RefreshRateConfigs::RefreshRateType;
-
 RefreshRateOverlay::RefreshRateOverlay(SurfaceFlinger& flinger)
       : mFlinger(flinger), mClient(new Client(&mFlinger)) {
     createLayer();
@@ -51,8 +49,8 @@
     return true;
 }
 
-void RefreshRateOverlay::changeRefreshRate(RefreshRateType type) {
-    const half3& color = (type == RefreshRateType::PERFORMANCE) ? GREEN : RED;
+void RefreshRateOverlay::changeRefreshRate(const RefreshRate& refreshRate) {
+    const half3& color = (refreshRate.fps > 65.0f) ? GREEN : RED;
     mLayer->setColor(color);
     mFlinger.mTransactionFlags.fetch_or(eTransactionMask);
 }
diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h
index ce29bc3..414bc47 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -19,13 +19,13 @@
 
 namespace android {
 
-using RefreshRateType = scheduler::RefreshRateConfigs::RefreshRateType;
+using RefreshRate = scheduler::RefreshRateConfigs::RefreshRate;
 
 class RefreshRateOverlay {
 public:
     RefreshRateOverlay(SurfaceFlinger& flinger);
 
-    void changeRefreshRate(RefreshRateType type);
+    void changeRefreshRate(const RefreshRate& refreshRate);
 
 private:
     bool createLayer();
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;
     };
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index bf3b4c9..5aa2447 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -542,14 +542,8 @@
 
         if (mRefreshRateConfigs->refreshRateSwitchingSupported()) {
             // set the refresh rate according to the policy
-            const auto& performanceRefreshRate =
-                    mRefreshRateConfigs->getRefreshRateFromType(RefreshRateType::PERFORMANCE);
-
-            if (isDisplayConfigAllowed(performanceRefreshRate.configId)) {
-                setRefreshRateTo(RefreshRateType::PERFORMANCE, Scheduler::ConfigEvent::None);
-            } else {
-                setRefreshRateTo(RefreshRateType::DEFAULT, Scheduler::ConfigEvent::None);
-            }
+            const auto& performanceRefreshRate = mRefreshRateConfigs->getMaxRefreshRateByPolicy();
+            changeRefreshRateLocked(performanceRefreshRate, Scheduler::ConfigEvent::None);
         }
     }));
 }
@@ -821,9 +815,8 @@
         info.xdpi = xdpi;
         info.ydpi = ydpi;
         info.fps = 1e9 / hwConfig->getVsyncPeriod();
-        const auto refreshRateType =
-                mRefreshRateConfigs->getRefreshRateTypeFromHwcConfigId(hwConfig->getId());
-        const auto offset = mPhaseOffsets->getOffsetsForRefreshRate(refreshRateType);
+
+        const auto offset = mPhaseOffsets->getOffsetsForRefreshRate(info.fps);
         info.appVsyncOffset = offset.late.app;
 
         // This is how far in advance a buffer must be queued for
@@ -873,17 +866,17 @@
     if (display->isPrimary()) {
         std::lock_guard<std::mutex> lock(mActiveConfigLock);
         if (mDesiredActiveConfigChanged) {
-            return mDesiredActiveConfig.configId;
-        } else {
-            return display->getActiveConfig();
+            return mDesiredActiveConfig.configId.value();
         }
-    } else {
-        return display->getActiveConfig();
     }
+
+    return display->getActiveConfig().value();
 }
 
 void SurfaceFlinger::setDesiredActiveConfig(const ActiveConfigInfo& info) {
     ATRACE_CALL();
+    auto refreshRate = mRefreshRateConfigs->getRefreshRateFromConfigId(info.configId);
+    ALOGV("setDesiredActiveConfig(%s)", refreshRate.name.c_str());
 
     // Don't check against the current mode yet. Worst case we set the desired
     // config twice. However event generation config might have changed so we need to update it
@@ -902,13 +895,14 @@
         // As we called to set period, we will call to onRefreshRateChangeCompleted once
         // DispSync model is locked.
         mVSyncModulator->onRefreshRateChangeInitiated();
-        mPhaseOffsets->setRefreshRateType(info.type);
+
+        mPhaseOffsets->setRefreshRateFps(refreshRate.fps);
         mVSyncModulator->setPhaseOffsets(mPhaseOffsets->getCurrentOffsets());
     }
     mDesiredActiveConfigChanged = true;
 
     if (mRefreshRateOverlay) {
-        mRefreshRateOverlay->changeRefreshRate(mDesiredActiveConfig.type);
+        mRefreshRateOverlay->changeRefreshRate(refreshRate);
     }
 }
 
@@ -930,14 +924,15 @@
     }
 
     std::lock_guard<std::mutex> lock(mActiveConfigLock);
-    mRefreshRateConfigs->setCurrentConfig(mUpcomingActiveConfig.configId);
+    mRefreshRateConfigs->setCurrentConfigId(mUpcomingActiveConfig.configId);
     mRefreshRateStats->setConfigMode(mUpcomingActiveConfig.configId);
-
     display->setActiveConfig(mUpcomingActiveConfig.configId);
 
-    mPhaseOffsets->setRefreshRateType(mUpcomingActiveConfig.type);
+    auto refreshRate =
+            mRefreshRateConfigs->getRefreshRateFromConfigId(mUpcomingActiveConfig.configId);
+    mPhaseOffsets->setRefreshRateFps(refreshRate.fps);
     mVSyncModulator->setPhaseOffsets(mPhaseOffsets->getCurrentOffsets());
-    ATRACE_INT("ActiveConfigMode", mUpcomingActiveConfig.configId);
+    ATRACE_INT("ActiveConfigFPS", refreshRate.fps);
 
     if (mUpcomingActiveConfig.event != Scheduler::ConfigEvent::None) {
         mScheduler->onConfigChanged(mAppConnectionHandle, display->getId()->value,
@@ -951,12 +946,15 @@
     mDesiredActiveConfigChanged = false;
 
     mScheduler->resyncToHardwareVsync(true, getVsyncPeriod());
-    mPhaseOffsets->setRefreshRateType(mUpcomingActiveConfig.type);
+    auto refreshRate =
+            mRefreshRateConfigs->getRefreshRateFromConfigId(mDesiredActiveConfig.configId);
+    mPhaseOffsets->setRefreshRateFps(refreshRate.fps);
     mVSyncModulator->setPhaseOffsets(mPhaseOffsets->getCurrentOffsets());
 }
 
 bool SurfaceFlinger::performSetActiveConfig() {
     ATRACE_CALL();
+    ALOGV("performSetActiveConfig");
     if (mCheckPendingFence) {
         if (previousFrameMissed()) {
             // fence has not signaled yet. wait for the next invalidate
@@ -980,6 +978,10 @@
         desiredActiveConfig = mDesiredActiveConfig;
     }
 
+    auto refreshRate =
+            mRefreshRateConfigs->getRefreshRateFromConfigId(desiredActiveConfig.configId);
+    ALOGV("performSetActiveConfig changing active config to %d(%s)", refreshRate.configId.value(),
+          refreshRate.name.c_str());
     const auto display = getDefaultDisplayDeviceLocked();
     if (!display || display->getActiveConfig() == desiredActiveConfig.configId) {
         // display is not valid or we are already in the requested mode
@@ -1000,8 +1002,8 @@
     const auto displayId = display->getId();
     LOG_ALWAYS_FATAL_IF(!displayId);
 
-    ATRACE_INT("ActiveConfigModeHWC", mUpcomingActiveConfig.configId);
-    getHwComposer().setActiveConfig(*displayId, mUpcomingActiveConfig.configId);
+    ATRACE_INT("ActiveConfigFPS_HWC", refreshRate.fps);
+    getHwComposer().setActiveConfig(*displayId, mUpcomingActiveConfig.configId.value());
 
     // we need to submit an empty frame to HWC to start the process
     mCheckPendingFence = true;
@@ -1396,11 +1398,12 @@
     *compositorTiming = getBE().mCompositorTiming;
 }
 
-bool SurfaceFlinger::isDisplayConfigAllowed(int32_t configId) const {
+bool SurfaceFlinger::isDisplayConfigAllowed(HwcConfigIndexType configId) const {
     return mAllowedDisplayConfigs.empty() || mAllowedDisplayConfigs.count(configId);
 }
 
-void SurfaceFlinger::setRefreshRateTo(RefreshRateType refreshRate, Scheduler::ConfigEvent event) {
+void SurfaceFlinger::changeRefreshRateLocked(const RefreshRate& refreshRate,
+                                             Scheduler::ConfigEvent event) {
     const auto display = getDefaultDisplayDeviceLocked();
     if (!display || mBootStage != BootStage::FINISHED) {
         return;
@@ -1408,15 +1411,13 @@
     ATRACE_CALL();
 
     // Don't do any updating if the current fps is the same as the new one.
-    const auto& refreshRateConfig = mRefreshRateConfigs->getRefreshRateFromType(refreshRate);
-    const int desiredConfigId = refreshRateConfig.configId;
-
-    if (!isDisplayConfigAllowed(desiredConfigId)) {
-        ALOGV("Skipping config %d as it is not part of allowed configs", desiredConfigId);
+    if (!isDisplayConfigAllowed(refreshRate.configId)) {
+        ALOGV("Skipping config %d as it is not part of allowed configs",
+              refreshRate.configId.value());
         return;
     }
 
-    setDesiredActiveConfig({refreshRate, desiredConfigId, event});
+    setDesiredActiveConfig({refreshRate.configId, event});
 }
 
 void SurfaceFlinger::onHotplugReceived(int32_t sequenceId, hwc2_display_t hwcDisplayId,
@@ -2180,7 +2181,8 @@
                                                     Dataspace::UNKNOWN});
     if (!state.isVirtual()) {
         LOG_ALWAYS_FATAL_IF(!displayId);
-        display->setActiveConfig(getHwComposer().getActiveConfigIndex(*displayId));
+        auto activeConfigId = HwcConfigIndexType(getHwComposer().getActiveConfigIndex(*displayId));
+        display->setActiveConfig(activeConfigId);
     }
 
     display->setLayerStack(state.layerStack);
@@ -2520,6 +2522,12 @@
     mCompositionEngine->updateCursorAsync(refreshArgs);
 }
 
+void SurfaceFlinger::changeRefreshRate(const RefreshRate& refreshRate,
+                                       Scheduler::ConfigEvent event) {
+    Mutex::Autolock lock(mStateLock);
+    changeRefreshRateLocked(refreshRate, event);
+}
+
 void SurfaceFlinger::initScheduler(DisplayId primaryDisplayId) {
     if (mScheduler) {
         // In practice it's not allowed to hotplug in/out the primary display once it's been
@@ -2528,7 +2536,7 @@
         return;
     }
 
-    int currentConfig = getHwComposer().getActiveConfigIndex(primaryDisplayId);
+    auto currentConfig = HwcConfigIndexType(getHwComposer().getActiveConfigIndex(primaryDisplayId));
     mRefreshRateConfigs =
             std::make_unique<scheduler::RefreshRateConfigs>(refresh_rate_switching(false),
                                                             getHwComposer().getConfigs(
@@ -2562,11 +2570,7 @@
             new RegionSamplingThread(*this, *mScheduler,
                                      RegionSamplingThread::EnvironmentTimingTunables());
 
-    mScheduler->setChangeRefreshRateCallback(
-            [this](RefreshRateType type, Scheduler::ConfigEvent event) {
-                Mutex::Autolock lock(mStateLock);
-                setRefreshRateTo(type, event);
-            });
+    mScheduler->setSchedulerCallback(this);
 }
 
 void SurfaceFlinger::commitTransaction()
@@ -3966,9 +3970,10 @@
                   dispSyncPresentTimeOffset, getVsyncPeriod());
 
     StringAppendF(&result, "Allowed Display Configs: ");
-    for (int32_t configId : mAllowedDisplayConfigs) {
+    for (auto configId : mAllowedDisplayConfigs) {
         StringAppendF(&result, "%" PRIu32 " Hz, ",
-                      mRefreshRateConfigs->getRefreshRateFromConfigId(configId).fps);
+                      static_cast<int32_t>(
+                              mRefreshRateConfigs->getRefreshRateFromConfigId(configId).fps));
     }
     StringAppendF(&result, "(config override by backdoor: %s)\n\n",
                   mDebugDisplayConfigSetByBackdoor ? "yes" : "no");
@@ -4809,13 +4814,9 @@
                 n = data.readInt32();
                 if (n && !mRefreshRateOverlay &&
                     mRefreshRateConfigs->refreshRateSwitchingSupported()) {
-                    RefreshRateType type;
-                    {
-                        std::lock_guard<std::mutex> lock(mActiveConfigLock);
-                        type = mDesiredActiveConfig.type;
-                    }
                     mRefreshRateOverlay = std::make_unique<RefreshRateOverlay>(*this);
-                    mRefreshRateOverlay->changeRefreshRate(type);
+                    auto current = mRefreshRateConfigs->getCurrentRefreshRate();
+                    mRefreshRateOverlay->changeRefreshRate(current);
                 } else if (!n) {
                     mRefreshRateOverlay.reset();
                 }
@@ -5417,28 +5418,48 @@
     mScheduler->onConfigChanged(mAppConnectionHandle, display->getId()->value,
                                 display->getActiveConfig());
 
-    if (mRefreshRateConfigs->refreshRateSwitchingSupported()) {
-        const auto& type = mScheduler->getPreferredRefreshRateType();
-        const auto& config = mRefreshRateConfigs->getRefreshRateFromType(type);
-        if (isDisplayConfigAllowed(config.configId)) {
-            ALOGV("switching to Scheduler preferred config %d", config.configId);
-            setDesiredActiveConfig({type, config.configId, Scheduler::ConfigEvent::Changed});
-        } else {
-            // Set the highest allowed config by iterating backwards on available refresh rates
-            const auto& refreshRates = mRefreshRateConfigs->getRefreshRateMap();
-            for (auto iter = refreshRates.crbegin(); iter != refreshRates.crend(); ++iter) {
-                if (isDisplayConfigAllowed(iter->second.configId)) {
-                    ALOGV("switching to allowed config %d", iter->second.configId);
-                    setDesiredActiveConfig(
-                            {iter->first, iter->second.configId, Scheduler::ConfigEvent::Changed});
-                    break;
-                }
-            }
+    // Prepare the parameters needed for RefreshRateConfigs::setPolicy. This will change to just
+    // passthrough once DisplayManager provide these parameters directly.
+    const auto refreshRate =
+            mRefreshRateConfigs->getRefreshRateFromConfigId(HwcConfigIndexType(allowedConfigs[0]));
+    const auto defaultModeId = refreshRate.configId;
+    auto minRefreshRateFps = refreshRate.fps;
+    auto maxRefreshRateFps = minRefreshRateFps;
+
+    for (auto config : allowedConfigs) {
+        const auto configRefreshRate =
+                mRefreshRateConfigs->getRefreshRateFromConfigId(HwcConfigIndexType(config));
+        if (configRefreshRate.fps < minRefreshRateFps) {
+            minRefreshRateFps = configRefreshRate.fps;
+        } else if (configRefreshRate.fps > maxRefreshRateFps) {
+            maxRefreshRateFps = configRefreshRate.fps;
         }
-    } else if (!allowedConfigs.empty()) {
-        ALOGV("switching to config %d", allowedConfigs[0]);
-        setDesiredActiveConfig(
-                {RefreshRateType::DEFAULT, allowedConfigs[0], Scheduler::ConfigEvent::Changed});
+    }
+    mRefreshRateConfigs->setPolicy(defaultModeId, minRefreshRateFps, maxRefreshRateFps);
+
+    if (mRefreshRateConfigs->refreshRateSwitchingSupported()) {
+        auto configId = mScheduler->getPreferredConfigId();
+        auto preferredRefreshRate = configId
+                ? mRefreshRateConfigs->getRefreshRateFromConfigId(*configId)
+                : mRefreshRateConfigs->getMinRefreshRateByPolicy();
+        ALOGV("trying to switch to Scheduler preferred config %d (%s)",
+              preferredRefreshRate.configId.value(), preferredRefreshRate.name.c_str());
+        if (isDisplayConfigAllowed(preferredRefreshRate.configId)) {
+            ALOGV("switching to Scheduler preferred config %d",
+                  preferredRefreshRate.configId.value());
+            setDesiredActiveConfig(
+                    {preferredRefreshRate.configId, Scheduler::ConfigEvent::Changed});
+        } else {
+            // Set the highest allowed config
+            setDesiredActiveConfig({mRefreshRateConfigs->getMaxRefreshRateByPolicy().configId,
+                                    Scheduler::ConfigEvent::Changed});
+        }
+    } else {
+        if (!allowedConfigs.empty()) {
+            ALOGV("switching to config %d", allowedConfigs[0]);
+            auto configId = HwcConfigIndexType(allowedConfigs[0]);
+            setDesiredActiveConfig({configId, Scheduler::ConfigEvent::Changed});
+        }
     }
 }
 
@@ -5487,7 +5508,10 @@
     }
 
     if (display->isPrimary()) {
-        outAllowedConfigs->assign(mAllowedDisplayConfigs.begin(), mAllowedDisplayConfigs.end());
+        outAllowedConfigs->reserve(mAllowedDisplayConfigs.size());
+        for (auto configId : mAllowedDisplayConfigs) {
+            outAllowedConfigs->push_back(configId.value());
+        }
     }
 
     return NO_ERROR;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 8e1199c..900c5f7 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -174,7 +174,8 @@
                        public PriorityDumper,
                        public ClientCache::ErasedRecipient,
                        private IBinder::DeathRecipient,
-                       private HWC2::ComposerCallback {
+                       private HWC2::ComposerCallback,
+                       private Scheduler::ISchedulerCallback {
 public:
     SurfaceFlingerBE& getBE() { return mBE; }
     const SurfaceFlingerBE& getBE() const { return mBE; }
@@ -495,6 +496,10 @@
             const hwc_vsync_period_change_timeline_t& updatedTimeline) override;
 
     /* ------------------------------------------------------------------------
+     * Scheduler::ISchedulerCallback
+     */
+    void changeRefreshRate(const Scheduler::RefreshRate&, Scheduler::ConfigEvent) override;
+    /* ------------------------------------------------------------------------
      * Message handling
      */
     void waitForEvent();
@@ -504,15 +509,14 @@
     void signalLayerUpdate();
     void signalRefresh();
 
-    using RefreshRateType = scheduler::RefreshRateConfigs::RefreshRateType;
+    using RefreshRate = scheduler::RefreshRateConfigs::RefreshRate;
 
     struct ActiveConfigInfo {
-        RefreshRateType type = RefreshRateType::DEFAULT;
-        int configId = 0;
+        HwcConfigIndexType configId;
         Scheduler::ConfigEvent event = Scheduler::ConfigEvent::None;
 
         bool operator!=(const ActiveConfigInfo& other) const {
-            return type != other.type || configId != other.configId || event != other.event;
+            return configId != other.configId || event != other.event;
         }
     };
 
@@ -787,9 +791,10 @@
 
     // Sets the refresh rate by switching active configs, if they are available for
     // the desired refresh rate.
-    void setRefreshRateTo(RefreshRateType, Scheduler::ConfigEvent event) REQUIRES(mStateLock);
+    void changeRefreshRateLocked(const RefreshRate&, Scheduler::ConfigEvent event)
+            REQUIRES(mStateLock);
 
-    bool isDisplayConfigAllowed(int32_t configId) const REQUIRES(mStateLock);
+    bool isDisplayConfigAllowed(HwcConfigIndexType configId) const REQUIRES(mStateLock);
 
     bool previousFrameMissed(int graceTimeMs = 0);
 
@@ -1113,7 +1118,7 @@
     std::atomic<nsecs_t> mExpectedPresentTime = 0;
 
     // All configs are allowed if the set is empty.
-    using DisplayConfigs = std::set<int32_t>;
+    using DisplayConfigs = std::set<HwcConfigIndexType>;
     DisplayConfigs mAllowedDisplayConfigs GUARDED_BY(mStateLock);
 
     std::mutex mActiveConfigLock;
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index db7d04c..76e8171 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -1427,7 +1427,7 @@
     // Note: This is not Case::Display::HWC_ACTIVE_CONFIG_ID as the ids are
     // remapped, and the test only ever sets up one config. If there were an error
     // looking up the remapped index, device->getActiveConfig() would be -1 instead.
-    EXPECT_EQ(0, device->getActiveConfig());
+    EXPECT_EQ(0, device->getActiveConfig().value());
     EXPECT_EQ(Case::PerFrameMetadataSupport::PER_FRAME_METADATA_KEYS,
               device->getSupportedPerFrameMetadata());
 }
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 2662f52..80bca02 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -26,6 +26,7 @@
 
 #include "AsyncCallRecorder.h"
 #include "Scheduler/EventThread.h"
+#include "Scheduler/HwcStrongTypes.h"
 
 using namespace std::chrono_literals;
 using namespace std::placeholders;
@@ -34,6 +35,7 @@
 using testing::Invoke;
 
 namespace android {
+
 namespace {
 
 constexpr PhysicalDisplayId INTERNAL_DISPLAY_ID = 111;
@@ -448,17 +450,17 @@
 }
 
 TEST_F(EventThreadTest, postConfigChangedPrimary) {
-    mThread->onConfigChanged(INTERNAL_DISPLAY_ID, 7);
+    mThread->onConfigChanged(INTERNAL_DISPLAY_ID, HwcConfigIndexType(7));
     expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 7);
 }
 
 TEST_F(EventThreadTest, postConfigChangedExternal) {
-    mThread->onConfigChanged(EXTERNAL_DISPLAY_ID, 5);
+    mThread->onConfigChanged(EXTERNAL_DISPLAY_ID, HwcConfigIndexType(5));
     expectConfigChangedEventReceivedByConnection(EXTERNAL_DISPLAY_ID, 5);
 }
 
 TEST_F(EventThreadTest, postConfigChangedPrimary64bit) {
-    mThread->onConfigChanged(DISPLAY_ID_64BIT, 7);
+    mThread->onConfigChanged(DISPLAY_ID_64BIT, HwcConfigIndexType(7));
     expectConfigChangedEventReceivedByConnection(DISPLAY_ID_64BIT, 7);
 }
 
@@ -468,7 +470,7 @@
             createConnection(suppressConnectionEventRecorder,
                              ISurfaceComposer::eConfigChangedSuppress);
 
-    mThread->onConfigChanged(INTERNAL_DISPLAY_ID, 9);
+    mThread->onConfigChanged(INTERNAL_DISPLAY_ID, HwcConfigIndexType(9));
     expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 9);
 
     auto args = suppressConnectionEventRecorder.waitForCall();
diff --git a/services/surfaceflinger/tests/unittests/FakePhaseOffsets.h b/services/surfaceflinger/tests/unittests/FakePhaseOffsets.h
index 66c7f6b..da4eea0 100644
--- a/services/surfaceflinger/tests/unittests/FakePhaseOffsets.h
+++ b/services/surfaceflinger/tests/unittests/FakePhaseOffsets.h
@@ -25,16 +25,16 @@
 struct FakePhaseOffsets : PhaseOffsets {
     static constexpr nsecs_t FAKE_PHASE_OFFSET_NS = 0;
 
-    Offsets getOffsetsForRefreshRate(RefreshRateType) const override { return getCurrentOffsets(); }
+    Offsets getOffsetsForRefreshRate(float) const override { return getCurrentOffsets(); }
 
     Offsets getCurrentOffsets() const override {
-        return {{RefreshRateType::DEFAULT, FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS},
-                {RefreshRateType::DEFAULT, FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS},
-                {RefreshRateType::DEFAULT, FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS},
+        return {{FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS},
+                {FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS},
+                {FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS},
                 FAKE_PHASE_OFFSET_NS};
     }
 
-    void setRefreshRateType(RefreshRateType) override {}
+    void setRefreshRateFps(float) override {}
     void dump(std::string&) const override {}
 };
 
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index e93d31e..d95252b 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -44,9 +44,15 @@
     auto createLayer() { return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger())); }
 
     RefreshRateConfigs mConfigs{true,
-                                {RefreshRateConfigs::InputConfig{0, LO_FPS_PERIOD},
-                                 RefreshRateConfigs::InputConfig{1, HI_FPS_PERIOD}},
-                                0};
+                                {
+                                        RefreshRateConfigs::InputConfig{HwcConfigIndexType(0),
+                                                                        HwcConfigGroupType(0),
+                                                                        LO_FPS_PERIOD},
+                                        RefreshRateConfigs::InputConfig{HwcConfigIndexType(1),
+                                                                        HwcConfigGroupType(0),
+                                                                        HI_FPS_PERIOD},
+                                },
+                                HwcConfigIndexType(0)};
     TestableScheduler* const mScheduler{new TestableScheduler(mConfigs)};
     TestableSurfaceFlinger mFlinger;
 
@@ -57,7 +63,6 @@
 
 TEST_F(LayerHistoryTest, oneLayer) {
     const auto layer = createLayer();
-    constexpr bool isHDR = false;
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
 
     EXPECT_EQ(1, layerCount());
@@ -69,14 +74,14 @@
 
     // 0 FPS is returned if active layers have insufficient history.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
-        history().record(layer.get(), 0, isHDR, mTime);
+        history().record(layer.get(), 0, mTime);
         EXPECT_FLOAT_EQ(0, history().summarize(mTime).maxRefreshRate);
         EXPECT_EQ(1, activeLayerCount());
     }
 
     // High FPS is returned once enough history has been recorded.
     for (int i = 0; i < 10; i++) {
-        history().record(layer.get(), 0, isHDR, mTime);
+        history().record(layer.get(), 0, mTime);
         EXPECT_FLOAT_EQ(HI_FPS, history().summarize(mTime).maxRefreshRate);
         EXPECT_EQ(1, activeLayerCount());
     }
@@ -84,29 +89,25 @@
 
 TEST_F(LayerHistoryTest, oneHDRLayer) {
     const auto layer = createLayer();
-    constexpr bool isHDR = true;
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
 
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
 
-    history().record(layer.get(), 0, isHDR, mTime);
+    history().record(layer.get(), 0, mTime);
     auto summary = history().summarize(mTime);
     EXPECT_FLOAT_EQ(0, summary.maxRefreshRate);
-    EXPECT_TRUE(summary.isHDR);
     EXPECT_EQ(1, activeLayerCount());
 
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
 
     summary = history().summarize(mTime);
     EXPECT_FLOAT_EQ(0, summary.maxRefreshRate);
-    EXPECT_FALSE(summary.isHDR);
     EXPECT_EQ(0, activeLayerCount());
 }
 
 TEST_F(LayerHistoryTest, explicitTimestamp) {
     const auto layer = createLayer();
-    constexpr bool isHDR = false;
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
 
     EXPECT_EQ(1, layerCount());
@@ -114,7 +115,7 @@
 
     nsecs_t time = mTime;
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer.get(), time, isHDR, time);
+        history().record(layer.get(), time, time);
         time += LO_FPS_PERIOD;
     }
 
@@ -127,7 +128,6 @@
     auto layer1 = createLayer();
     auto layer2 = createLayer();
     auto layer3 = createLayer();
-    constexpr bool isHDR = false;
 
     EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer2, isVisible()).WillRepeatedly(Return(true));
@@ -141,7 +141,7 @@
 
     // layer1 is active but infrequent.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer1.get(), time, isHDR, time);
+        history().record(layer1.get(), time, time);
         time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
     }
 
@@ -151,12 +151,12 @@
 
     // layer2 is frequent and has high refresh rate.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer2.get(), time, isHDR, time);
+        history().record(layer2.get(), time, time);
         time += HI_FPS_PERIOD;
     }
 
     // layer1 is still active but infrequent.
-    history().record(layer1.get(), time, isHDR, time);
+    history().record(layer1.get(), time, time);
 
     EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time).maxRefreshRate);
     EXPECT_EQ(2, activeLayerCount());
@@ -165,7 +165,7 @@
     // layer1 is no longer active.
     // layer2 is frequent and has low refresh rate.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer2.get(), time, isHDR, time);
+        history().record(layer2.get(), time, time);
         time += LO_FPS_PERIOD;
     }
 
@@ -178,10 +178,10 @@
     constexpr int RATIO = LO_FPS_PERIOD / HI_FPS_PERIOD;
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
         if (i % RATIO == 0) {
-            history().record(layer2.get(), time, isHDR, time);
+            history().record(layer2.get(), time, time);
         }
 
-        history().record(layer3.get(), time, isHDR, time);
+        history().record(layer3.get(), time, time);
         time += HI_FPS_PERIOD;
     }
 
@@ -190,7 +190,7 @@
     EXPECT_EQ(2, frequentLayerCount(time));
 
     // layer3 becomes recently active.
-    history().record(layer3.get(), time, isHDR, time);
+    history().record(layer3.get(), time, time);
     EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time).maxRefreshRate);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
@@ -205,7 +205,7 @@
     // layer2 still has low refresh rate.
     // layer3 becomes inactive.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer2.get(), time, isHDR, time);
+        history().record(layer2.get(), time, time);
         time += LO_FPS_PERIOD;
     }
 
@@ -222,7 +222,7 @@
 
     // layer3 becomes active and has high refresh rate.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer3.get(), time, isHDR, time);
+        history().record(layer3.get(), time, time);
         time += HI_FPS_PERIOD;
     }
 
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index f315a8a..546e65c 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -30,27 +30,19 @@
 namespace android {
 namespace scheduler {
 
-using RefreshRateType = RefreshRateConfigs::RefreshRateType;
 using RefreshRate = RefreshRateConfigs::RefreshRate;
 
 class RefreshRateConfigsTest : public testing::Test {
 protected:
-    static constexpr int CONFIG_ID_60 = 0;
-    static constexpr hwc2_config_t HWC2_CONFIG_ID_60 = 0;
-    static constexpr int CONFIG_ID_90 = 1;
-    static constexpr hwc2_config_t HWC2_CONFIG_ID_90 = 1;
+    static inline const HwcConfigIndexType HWC_CONFIG_ID_60 = HwcConfigIndexType(0);
+    static inline const HwcConfigIndexType HWC_CONFIG_ID_90 = HwcConfigIndexType(1);
+    static inline const HwcConfigGroupType HWC_GROUP_ID_0 = HwcConfigGroupType(0);
+    static inline const HwcConfigGroupType HWC_GROUP_ID_1 = HwcConfigGroupType(1);
     static constexpr int64_t VSYNC_60 = 16666667;
     static constexpr int64_t VSYNC_90 = 11111111;
 
     RefreshRateConfigsTest();
     ~RefreshRateConfigsTest();
-
-    void assertRatesEqual(const RefreshRate& left, const RefreshRate& right) {
-        ASSERT_EQ(left.configId, right.configId);
-        ASSERT_EQ(left.name, right.name);
-        ASSERT_EQ(left.fps, right.fps);
-        ASSERT_EQ(left.vsyncPeriod, right.vsyncPeriod);
-    }
 };
 
 RefreshRateConfigsTest::RefreshRateConfigsTest() {
@@ -69,40 +61,173 @@
 /* ------------------------------------------------------------------------
  * Test cases
  */
-TEST_F(RefreshRateConfigsTest, oneDeviceConfig_isRejected) {
-    std::vector<RefreshRateConfigs::InputConfig> configs{{HWC2_CONFIG_ID_60, VSYNC_60}};
+TEST_F(RefreshRateConfigsTest, oneDeviceConfig_SwitchingSupported) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60}}};
     auto refreshRateConfigs =
             std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
-                                                 /*currentConfig=*/0);
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+}
+
+TEST_F(RefreshRateConfigsTest, oneDeviceConfig_SwitchingNotSupported) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/false, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
     ASSERT_FALSE(refreshRateConfigs->refreshRateSwitchingSupported());
 }
 
 TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_storesFullRefreshRateMap) {
-    std::vector<RefreshRateConfigs::InputConfig> configs{{HWC2_CONFIG_ID_60, VSYNC_60},
-                                                         {HWC2_CONFIG_ID_90, VSYNC_90}};
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_0, VSYNC_90}}};
     auto refreshRateConfigs =
             std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
-                                                 /*currentConfig=*/0);
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
 
     ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
-    const auto& rates = refreshRateConfigs->getRefreshRateMap();
-    ASSERT_EQ(2, rates.size());
-    const auto& defaultRate = rates.find(RefreshRateType::DEFAULT);
-    const auto& performanceRate = rates.find(RefreshRateType::PERFORMANCE);
-    ASSERT_NE(rates.end(), defaultRate);
-    ASSERT_NE(rates.end(), performanceRate);
 
-    RefreshRate expectedDefaultConfig = {CONFIG_ID_60, "60fps", 60, VSYNC_60, HWC2_CONFIG_ID_60};
-    assertRatesEqual(expectedDefaultConfig, defaultRate->second);
-    RefreshRate expectedPerformanceConfig = {CONFIG_ID_90, "90fps", 90, VSYNC_90,
-                                             HWC2_CONFIG_ID_90};
-    assertRatesEqual(expectedPerformanceConfig, performanceRate->second);
+    const auto minRate = refreshRateConfigs->getMinRefreshRate();
+    const auto performanceRate = refreshRateConfigs->getMaxRefreshRate();
 
-    assertRatesEqual(expectedDefaultConfig,
-                     refreshRateConfigs->getRefreshRateFromType(RefreshRateType::DEFAULT));
-    assertRatesEqual(expectedPerformanceConfig,
-                     refreshRateConfigs->getRefreshRateFromType(RefreshRateType::PERFORMANCE));
+    RefreshRate expectedDefaultConfig = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    ASSERT_EQ(expectedDefaultConfig, minRate);
+    RefreshRate expectedPerformanceConfig = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_0, "90fps",
+                                             90};
+    ASSERT_EQ(expectedPerformanceConfig, performanceRate);
+
+    const auto minRateByPolicy = refreshRateConfigs->getMinRefreshRateByPolicy();
+    const auto performanceRateByPolicy = refreshRateConfigs->getMaxRefreshRateByPolicy();
+    ASSERT_EQ(minRateByPolicy, minRate);
+    ASSERT_EQ(performanceRateByPolicy, performanceRate);
 }
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_storesFullRefreshRateMap_differentGroups) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_1, VSYNC_90}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+    const auto minRate = refreshRateConfigs->getMinRefreshRateByPolicy();
+    const auto performanceRate = refreshRateConfigs->getMaxRefreshRate();
+    const auto minRate60 = refreshRateConfigs->getMinRefreshRateByPolicy();
+    const auto performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
+
+    RefreshRate expectedDefaultConfig = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    ASSERT_EQ(expectedDefaultConfig, minRate);
+    ASSERT_EQ(expectedDefaultConfig, minRate60);
+    ASSERT_EQ(expectedDefaultConfig, performanceRate60);
+
+    refreshRateConfigs->setPolicy(HWC_CONFIG_ID_90, 60, 90);
+    refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
+
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+    const auto minRate90 = refreshRateConfigs->getMinRefreshRateByPolicy();
+    const auto performanceRate90 = refreshRateConfigs->getMaxRefreshRateByPolicy();
+
+    RefreshRate expectedPerformanceConfig = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_1, "90fps",
+                                             90};
+    ASSERT_EQ(expectedPerformanceConfig, performanceRate);
+    ASSERT_EQ(expectedPerformanceConfig, minRate90);
+    ASSERT_EQ(expectedPerformanceConfig, performanceRate90);
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_policyChange) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_0, VSYNC_90}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+    auto minRate = refreshRateConfigs->getMinRefreshRateByPolicy();
+    auto performanceRate = refreshRateConfigs->getMaxRefreshRateByPolicy();
+
+    RefreshRate expectedDefaultConfig = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    ASSERT_EQ(expectedDefaultConfig, minRate);
+    RefreshRate expectedPerformanceConfig = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_0, "90fps",
+                                             90};
+    ASSERT_EQ(expectedPerformanceConfig, performanceRate);
+
+    refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 60, 60);
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+
+    auto minRate60 = refreshRateConfigs->getMinRefreshRateByPolicy();
+    auto performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
+    ASSERT_EQ(expectedDefaultConfig, minRate60);
+    ASSERT_EQ(expectedDefaultConfig, performanceRate60);
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getCurrentRefreshRate) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_0, VSYNC_90}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    {
+        auto current = refreshRateConfigs->getCurrentRefreshRate();
+        EXPECT_EQ(current.configId, HWC_CONFIG_ID_60);
+    }
+
+    refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
+    {
+        auto current = refreshRateConfigs->getCurrentRefreshRate();
+        EXPECT_EQ(current.configId, HWC_CONFIG_ID_90);
+    }
+
+    refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 90, 90);
+    {
+        auto current = refreshRateConfigs->getCurrentRefreshRate();
+        EXPECT_EQ(current.configId, HWC_CONFIG_ID_90);
+    }
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContent) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_0, VSYNC_90}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+
+    RefreshRate expected60Config = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    RefreshRate expected90Config = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_0, "90fps", 90};
+
+    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(90.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(60.0f));
+    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(45.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(30.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(24.0f));
+
+    refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 60, 60);
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(90.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(60.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(45.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(30.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(24.0f));
+
+    refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 90, 90);
+    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(90.0f));
+    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(60.0f));
+    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(45.0f));
+    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(30.0f));
+    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(24.0f));
+    refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 0, 120);
+    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(90.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(60.0f));
+    ASSERT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(45.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(30.0f));
+    ASSERT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(24.0f));
+}
+
 } // namespace
 } // namespace scheduler
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
index cec0b32..ef4699f 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
@@ -33,8 +33,9 @@
 
 class RefreshRateStatsTest : public testing::Test {
 protected:
-    static constexpr int CONFIG_ID_90 = 0;
-    static constexpr int CONFIG_ID_60 = 1;
+    static inline const auto CONFIG_ID_0 = HwcConfigIndexType(0);
+    static inline const auto CONFIG_ID_1 = HwcConfigIndexType(1);
+    static inline const auto CONFIG_GROUP_0 = HwcConfigGroupType(0);
     static constexpr int64_t VSYNC_90 = 11111111;
     static constexpr int64_t VSYNC_60 = 16666667;
 
@@ -43,10 +44,10 @@
 
     void init(const std::vector<RefreshRateConfigs::InputConfig>& configs) {
         mRefreshRateConfigs = std::make_unique<RefreshRateConfigs>(
-                /*refreshRateSwitching=*/true, configs, /*currentConfig=*/0);
+                /*refreshRateSwitching=*/true, configs, /*currentConfig=*/CONFIG_ID_0);
         mRefreshRateStats =
                 std::make_unique<RefreshRateStats>(*mRefreshRateConfigs, mTimeStats,
-                                                   /*currentConfig=*/0,
+                                                   /*currentConfigId=*/CONFIG_ID_0,
                                                    /*currentPowerMode=*/HWC_POWER_MODE_OFF);
     }
 
@@ -72,7 +73,7 @@
  * Test cases
  */
 TEST_F(RefreshRateStatsTest, oneConfigTest) {
-    init({{CONFIG_ID_90, VSYNC_90}});
+    init({{{CONFIG_ID_0, CONFIG_GROUP_0, VSYNC_90}}});
 
     EXPECT_CALL(mTimeStats, recordRefreshRate(0, _)).Times(AtLeast(1));
     EXPECT_CALL(mTimeStats, recordRefreshRate(90, _)).Times(AtLeast(1));
@@ -91,7 +92,7 @@
     EXPECT_LT(screenOff, times["ScreenOff"]);
     EXPECT_EQ(0u, times.count("90fps"));
 
-    mRefreshRateStats->setConfigMode(CONFIG_ID_90);
+    mRefreshRateStats->setConfigMode(CONFIG_ID_0);
     mRefreshRateStats->setPowerMode(HWC_POWER_MODE_NORMAL);
     screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
@@ -107,7 +108,7 @@
     EXPECT_LT(screenOff, times["ScreenOff"]);
     EXPECT_EQ(ninety, times["90fps"]);
 
-    mRefreshRateStats->setConfigMode(CONFIG_ID_90);
+    mRefreshRateStats->setConfigMode(CONFIG_ID_0);
     screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
@@ -118,7 +119,7 @@
 }
 
 TEST_F(RefreshRateStatsTest, twoConfigsTest) {
-    init({{CONFIG_ID_90, VSYNC_90}, {CONFIG_ID_60, VSYNC_60}});
+    init({{{CONFIG_ID_0, CONFIG_GROUP_0, VSYNC_90}, {CONFIG_ID_1, CONFIG_GROUP_0, VSYNC_60}}});
 
     EXPECT_CALL(mTimeStats, recordRefreshRate(0, _)).Times(AtLeast(1));
     EXPECT_CALL(mTimeStats, recordRefreshRate(60, _)).Times(AtLeast(1));
@@ -137,7 +138,7 @@
     times = mRefreshRateStats->getTotalTimes();
     EXPECT_LT(screenOff, times["ScreenOff"]);
 
-    mRefreshRateStats->setConfigMode(CONFIG_ID_90);
+    mRefreshRateStats->setConfigMode(CONFIG_ID_0);
     mRefreshRateStats->setPowerMode(HWC_POWER_MODE_NORMAL);
     screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
@@ -147,7 +148,7 @@
     EXPECT_LT(0, times["90fps"]);
 
     // When power mode is normal, time for configs updates.
-    mRefreshRateStats->setConfigMode(CONFIG_ID_60);
+    mRefreshRateStats->setConfigMode(CONFIG_ID_1);
     int ninety = mRefreshRateStats->getTotalTimes()["90fps"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
@@ -156,7 +157,7 @@
     ASSERT_EQ(1u, times.count("60fps"));
     EXPECT_LT(0, times["60fps"]);
 
-    mRefreshRateStats->setConfigMode(CONFIG_ID_90);
+    mRefreshRateStats->setConfigMode(CONFIG_ID_0);
     int sixty = mRefreshRateStats->getTotalTimes()["60fps"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
@@ -164,7 +165,7 @@
     EXPECT_LT(ninety, times["90fps"]);
     EXPECT_EQ(sixty, times["60fps"]);
 
-    mRefreshRateStats->setConfigMode(CONFIG_ID_60);
+    mRefreshRateStats->setConfigMode(CONFIG_ID_1);
     ninety = mRefreshRateStats->getTotalTimes()["90fps"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
@@ -175,7 +176,7 @@
     // Because the power mode is not HWC_POWER_MODE_NORMAL, switching the config
     // does not update refresh rates that come from the config.
     mRefreshRateStats->setPowerMode(HWC_POWER_MODE_DOZE);
-    mRefreshRateStats->setConfigMode(CONFIG_ID_90);
+    mRefreshRateStats->setConfigMode(CONFIG_ID_0);
     sixty = mRefreshRateStats->getTotalTimes()["60fps"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
@@ -183,7 +184,7 @@
     EXPECT_EQ(ninety, times["90fps"]);
     EXPECT_EQ(sixty, times["60fps"]);
 
-    mRefreshRateStats->setConfigMode(CONFIG_ID_60);
+    mRefreshRateStats->setConfigMode(CONFIG_ID_1);
     screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index b4cc1e1..40536ab 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -50,10 +50,11 @@
             ::testing::UnitTest::GetInstance()->current_test_info();
     ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
 
-    std::vector<scheduler::RefreshRateConfigs::InputConfig> configs{{/*hwcId=*/0, 16666667}};
-    mRefreshRateConfigs =
-            std::make_unique<scheduler::RefreshRateConfigs>(/*refreshRateSwitching=*/false, configs,
-                                                            /*currentConfig=*/0);
+    std::vector<scheduler::RefreshRateConfigs::InputConfig> configs{
+            {{HwcConfigIndexType(0), HwcConfigGroupType(0), 16666667}}};
+    mRefreshRateConfigs = std::make_unique<
+            scheduler::RefreshRateConfigs>(/*refreshRateSwitching=*/false, configs,
+                                           /*currentConfig=*/HwcConfigIndexType(0));
 
     mScheduler = std::make_unique<TestableScheduler>(*mRefreshRateConfigs);
 
diff --git a/services/surfaceflinger/tests/unittests/StrongTypingTest.cpp b/services/surfaceflinger/tests/unittests/StrongTypingTest.cpp
index b9ddcd7..5406879 100644
--- a/services/surfaceflinger/tests/unittests/StrongTypingTest.cpp
+++ b/services/surfaceflinger/tests/unittests/StrongTypingTest.cpp
@@ -56,18 +56,18 @@
     EXPECT_THAT(f1 + f2, Eq(FunkyType(32)));
     EXPECT_THAT(f2 + f1, Eq(FunkyType(32)));
 
-    EXPECT_THAT(++f1, Eq(11));
-    EXPECT_THAT(f1, Eq(11));
-    EXPECT_THAT(f1++, Eq(11));
-    EXPECT_THAT(f1++, Eq(12));
-    EXPECT_THAT(f1, Eq(13));
+    EXPECT_THAT(++f1.value(), Eq(11));
+    EXPECT_THAT(f1.value(), Eq(11));
+    EXPECT_THAT(f1++.value(), Eq(11));
+    EXPECT_THAT(f1++.value(), Eq(12));
+    EXPECT_THAT(f1.value(), Eq(13));
 
     auto f3 = f1;
     EXPECT_THAT(f1, Eq(f3));
     EXPECT_THAT(f1, Lt(f2));
 
     f3 += f1;
-    EXPECT_THAT(f1, Eq(13));
-    EXPECT_THAT(f3, Eq(26));
+    EXPECT_THAT(f1.value(), Eq(13));
+    EXPECT_THAT(f3.value(), Eq(26));
 }
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 94fc5f7..c4b8408 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -193,15 +193,15 @@
                         std::unique_ptr<EventControlThread> eventControlThread,
                         std::unique_ptr<EventThread> appEventThread,
                         std::unique_ptr<EventThread> sfEventThread) {
-        std::vector<scheduler::RefreshRateConfigs::InputConfig> configs{{/*hwcId=*/0, 16666667}};
-        mFlinger->mRefreshRateConfigs =
-                std::make_unique<scheduler::RefreshRateConfigs>(/*refreshRateSwitching=*/false,
-                                                                configs, /*currentConfig=*/0);
-        mFlinger->mRefreshRateStats =
-                std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mRefreshRateConfigs,
-                                                              *mFlinger->mTimeStats,
-                                                              /*currentConfig=*/0,
-                                                              /*powerMode=*/HWC_POWER_MODE_OFF);
+        std::vector<scheduler::RefreshRateConfigs::InputConfig> configs{
+                {{HwcConfigIndexType(0), HwcConfigGroupType(0), 16666667}}};
+        mFlinger->mRefreshRateConfigs = std::make_unique<
+                scheduler::RefreshRateConfigs>(/*refreshRateSwitching=*/false, configs,
+                                               /*currentConfig=*/HwcConfigIndexType(0));
+        mFlinger->mRefreshRateStats = std::make_unique<
+                scheduler::RefreshRateStats>(*mFlinger->mRefreshRateConfigs, *mFlinger->mTimeStats,
+                                             /*currentConfig=*/HwcConfigIndexType(0),
+                                             /*powerMode=*/HWC_POWER_MODE_OFF);
 
         mScheduler =
                 new TestableScheduler(std::move(primaryDispSync), std::move(eventControlThread),
@@ -429,6 +429,7 @@
         static constexpr int32_t DEFAULT_WIDTH = 1920;
         static constexpr int32_t DEFAULT_HEIGHT = 1280;
         static constexpr int32_t DEFAULT_REFRESH_RATE = 16'666'666;
+        static constexpr int32_t DEFAULT_CONFIG_GROUP = 7;
         static constexpr int32_t DEFAULT_DPI = 320;
         static constexpr int32_t DEFAULT_ACTIVE_CONFIG = 0;
         static constexpr int32_t DEFAULT_POWER_MODE = 2;
@@ -452,7 +453,7 @@
             return *this;
         }
 
-        auto& setRefreshRate(int32_t refreshRate) {
+        auto& setRefreshRate(uint32_t refreshRate) {
             mRefreshRate = refreshRate;
             return *this;
         }
@@ -499,6 +500,7 @@
             config.setVsyncPeriod(mRefreshRate);
             config.setDpiX(mDpiX);
             config.setDpiY(mDpiY);
+            config.setConfigGroup(mConfigGroup);
             display->mutableConfigs().emplace(mActiveConfig, config.build());
             display->mutableIsConnected() = true;
             display->setPowerMode(static_cast<HWC2::PowerMode>(mPowerMode));
@@ -522,8 +524,9 @@
         hwc2_display_t mHwcDisplayId = DEFAULT_HWC_DISPLAY_ID;
         int32_t mWidth = DEFAULT_WIDTH;
         int32_t mHeight = DEFAULT_HEIGHT;
-        int32_t mRefreshRate = DEFAULT_REFRESH_RATE;
+        uint32_t mRefreshRate = DEFAULT_REFRESH_RATE;
         int32_t mDpiX = DEFAULT_DPI;
+        int32_t mConfigGroup = DEFAULT_CONFIG_GROUP;
         int32_t mDpiY = DEFAULT_DPI;
         int32_t mActiveConfig = DEFAULT_ACTIVE_CONFIG;
         int32_t mPowerMode = DEFAULT_POWER_MODE;
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index ed35ebf..f7c3804 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -33,7 +33,7 @@
     MOCK_METHOD0(onScreenReleased, void());
     MOCK_METHOD0(onScreenAcquired, void());
     MOCK_METHOD2(onHotplugReceived, void(PhysicalDisplayId, bool));
-    MOCK_METHOD2(onConfigChanged, void(PhysicalDisplayId, int32_t));
+    MOCK_METHOD2(onConfigChanged, void(PhysicalDisplayId, HwcConfigIndexType));
     MOCK_CONST_METHOD1(dump, void(std::string&));
     MOCK_METHOD1(setPhaseOffset, void(nsecs_t phaseOffset));
     MOCK_METHOD1(registerDisplayEventConnection,