Merge "SurfaceFlinger: enhance refresh rate selection"
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 9a7eabd..1b1e889 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -165,7 +165,9 @@
         "Scheduler/EventThread.cpp",
         "Scheduler/OneShotTimer.cpp",
         "Scheduler/LayerHistory.cpp",
+        "Scheduler/LayerHistoryV2.cpp",
         "Scheduler/LayerInfo.cpp",
+        "Scheduler/LayerInfoV2.cpp",
         "Scheduler/MessageQueue.cpp",
         "Scheduler/PhaseOffsets.cpp",
         "Scheduler/RefreshRateConfigs.cpp",
diff --git a/services/surfaceflinger/BufferQueueLayer.cpp b/services/surfaceflinger/BufferQueueLayer.cpp
index b55e62b..e85281d 100644
--- a/services/surfaceflinger/BufferQueueLayer.cpp
+++ b/services/surfaceflinger/BufferQueueLayer.cpp
@@ -129,8 +129,11 @@
     return frameRateChanged;
 }
 
-float BufferQueueLayer::getFrameRate() const {
-    return mLatchedFrameRate;
+std::optional<float> BufferQueueLayer::getFrameRate() const {
+    if (mLatchedFrameRate > 0.f || mLatchedFrameRate == FRAME_RATE_NO_VOTE)
+        return mLatchedFrameRate;
+
+    return {};
 }
 
 // -----------------------------------------------------------------------
diff --git a/services/surfaceflinger/BufferQueueLayer.h b/services/surfaceflinger/BufferQueueLayer.h
index 0777953..2bd1e3d 100644
--- a/services/surfaceflinger/BufferQueueLayer.h
+++ b/services/surfaceflinger/BufferQueueLayer.h
@@ -57,7 +57,7 @@
     bool shouldPresentNow(nsecs_t expectedPresentTime) const override;
 
     bool setFrameRate(float frameRate) override;
-    float getFrameRate() const override;
+    std::optional<float> getFrameRate() const override;
 
     // -----------------------------------------------------------------------
 
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index 287fe89..cd4227c 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -253,7 +253,8 @@
                                            FrameTracer::FrameEvent::POST);
     mCurrentState.desiredPresentTime = desiredPresentTime;
 
-    mFlinger->mScheduler->recordLayerHistory(this, desiredPresentTime);
+    mFlinger->mScheduler->recordLayerHistory(this,
+                                             desiredPresentTime <= 0 ? 0 : desiredPresentTime);
 
     return true;
 }
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 605e7c8..ff48ecd 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -227,8 +227,10 @@
 public:
     DisplayRenderArea(const sp<const DisplayDevice>& display,
                       RotationFlags rotation = ui::Transform::ROT_0)
-          : DisplayRenderArea(display, display->getBounds(), display->getWidth(),
-                              display->getHeight(), display->getCompositionDataSpace(), rotation) {}
+          : DisplayRenderArea(display, display->getBounds(),
+                              static_cast<uint32_t>(display->getWidth()),
+                              static_cast<uint32_t>(display->getHeight()),
+                              display->getCompositionDataSpace(), rotation) {}
 
     DisplayRenderArea(sp<const DisplayDevice> display, const Rect& sourceCrop, uint32_t reqWidth,
                       uint32_t reqHeight, ui::Dataspace reqDataSpace, RotationFlags rotation,
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index eef0c8b..aa43f09 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -23,6 +23,10 @@
 #include <utility>
 #include <vector>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
 #if defined(USE_VR_COMPOSER) && USE_VR_COMPOSER
 #include <android/frameworks/vr/composer/2.0/IVrComposerClient.h>
 #endif // defined(USE_VR_COMPOSER) && USE_VR_COMPOSER
@@ -36,6 +40,9 @@
 #include <ui/GraphicBuffer.h>
 #include <utils/StrongPointer.h>
 
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
 namespace android {
 
 namespace Hwc2 {
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index a0dabb4..effe43b 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -27,7 +27,13 @@
 
 #include <android-base/thread_annotations.h>
 #include <ui/Fence.h>
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
 #include <ui/GraphicTypes.h>
+#pragma clang diagnostic pop
+
 #include <utils/StrongPointer.h>
 #include <utils/Timers.h>
 
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 67810fc..6f8df95 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -1256,8 +1256,11 @@
     return true;
 }
 
-float Layer::getFrameRate() const {
-    return getDrawingState().frameRate;
+std::optional<float> Layer::getFrameRate() const {
+    const auto frameRate = getDrawingState().frameRate;
+    if (frameRate > 0.f || frameRate == FRAME_RATE_NO_VOTE) return frameRate;
+
+    return {};
 }
 
 void Layer::deferTransactionUntil_legacy(const sp<Layer>& barrierLayer, uint64_t frameNumber) {
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index fae976d..c75a570 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -744,8 +744,9 @@
      */
     Rect getCroppedBufferSize(const Layer::State& s) const;
 
+    constexpr static auto FRAME_RATE_NO_VOTE = -1.0f;
     virtual bool setFrameRate(float frameRate);
-    virtual float getFrameRate() const;
+    virtual std::optional<float> getFrameRate() const;
 
 protected:
     // constant
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index 33e5796..d3d9d3a 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -155,6 +155,7 @@
 
     Mutex::Autolock _l(mFlinger.mStateLock);
     mLayer = mClient->getLayerUser(mIBinder);
+    mLayer->setFrameRate(Layer::FRAME_RATE_NO_VOTE);
 
     // setting Layer's Z requires resorting layersSortedByZ
     ssize_t idx = mFlinger.mCurrentState.layersSortedByZ.indexOf(mLayer);
diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h
index a7a6dd5..9b3a9f4 100644
--- a/services/surfaceflinger/RenderArea.h
+++ b/services/surfaceflinger/RenderArea.h
@@ -71,8 +71,8 @@
     RotationFlags getRotationFlags() const { return mRotationFlags; }
 
     // Returns the size of the physical render area.
-    int getReqWidth() const { return mReqWidth; }
-    int getReqHeight() const { return mReqHeight; }
+    int getReqWidth() const { return static_cast<int>(mReqWidth); }
+    int getReqHeight() const { return static_cast<int>(mReqHeight); }
 
     // Returns the composition data space of the render area.
     ui::Dataspace getReqDataSpace() const { return mReqDataSpace; }
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index abf0cd6..ebd617f 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -43,7 +43,7 @@
 namespace {
 
 bool isLayerActive(const Layer& layer, const LayerInfo& info, nsecs_t threshold) {
-    if (layer.getFrameRate() > .0f) {
+    if (layer.getFrameRate().has_value()) {
         return layer.isVisible();
     }
     return layer.isVisible() && info.getLastUpdatedTime() >= threshold;
@@ -77,7 +77,8 @@
       : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {}
 LayerHistory::~LayerHistory() = default;
 
-void LayerHistory::registerLayer(Layer* layer, float lowRefreshRate, float highRefreshRate) {
+void LayerHistory::registerLayer(Layer* layer, float lowRefreshRate, float highRefreshRate,
+                                 LayerVoteType /*type*/) {
     auto info = std::make_unique<LayerInfo>(lowRefreshRate, highRefreshRate);
     std::lock_guard lock(mLock);
     mLayerInfos.emplace_back(layer, std::move(info));
@@ -101,8 +102,6 @@
 }
 
 LayerHistory::Summary LayerHistory::summarize(nsecs_t now) {
-    float maxRefreshRate = 0;
-
     std::lock_guard lock(mLock);
 
     partitionLayers(now);
@@ -113,7 +112,7 @@
 
         if (recent || CC_UNLIKELY(mTraceEnabled)) {
             const float refreshRate = info->getRefreshRate(now);
-            if (recent && refreshRate > maxRefreshRate) {
+            if (recent && refreshRate > 0.0f) {
                 if (const auto layer = activeLayer.promote(); layer) {
                     const int32_t priority = layer->getFrameRateSelectionPriority();
                     // TODO(b/142507166): This is where the scoring algorithm should live.
@@ -124,36 +123,30 @@
         }
     }
 
+    LayerHistory::Summary summary;
     for (const auto& [weakLayer, info] : activeLayers()) {
         const bool recent = info->isRecentlyActive(now);
         auto layer = weakLayer.promote();
         // Only use the layer if the reference still exists.
         if (layer || CC_UNLIKELY(mTraceEnabled)) {
-            float refreshRate = 0.f;
-            // Default content refresh rate is only used when dealing with recent layers.
-            if (recent) {
-                refreshRate = info->getRefreshRate(now);
-            }
             // Check if frame rate was set on layer.
-            float frameRate = layer->getFrameRate();
-            if (frameRate > 0.f) {
-                // Override content detection refresh rate, if it was set.
-                refreshRate = frameRate;
-            }
-            if (refreshRate > maxRefreshRate) {
-                maxRefreshRate = refreshRate;
+            auto frameRate = layer->getFrameRate();
+            if (frameRate.has_value() && frameRate.value() > 0.f) {
+                summary.push_back(
+                        {layer->getName(), LayerVoteType::Explicit, *frameRate, /* weight */ 1.0f});
+            } else if (recent) {
+                frameRate = info->getRefreshRate(now);
+                summary.push_back({layer->getName(), LayerVoteType::Heuristic, *frameRate,
+                                   /* weight */ 1.0f});
             }
 
             if (CC_UNLIKELY(mTraceEnabled)) {
-                trace(weakLayer, std::round(refreshRate));
+                trace(weakLayer, std::round(*frameRate));
             }
         }
     }
-    if (CC_UNLIKELY(mTraceEnabled)) {
-        ALOGD("%s: maxRefreshRate=%.2f", __FUNCTION__, maxRefreshRate);
-    }
 
-    return {maxRefreshRate};
+    return summary;
 }
 
 void LayerHistory::partitionLayers(nsecs_t now) {
@@ -199,22 +192,6 @@
     mActiveLayersEnd = 0;
 }
 
-bool LayerHistory::hasClientSpecifiedFrameRate() {
-    std::lock_guard lock(mLock);
-    for (const auto& [weakLayer, info] : activeLayers()) {
-        auto layer = weakLayer.promote();
-        if (layer) {
-            float frameRate = layer->getFrameRate();
-            // Found a layer that has a frame rate set on it.
-            if (fabs(frameRate) > 0.f) {
-                return true;
-            }
-        }
-    }
-    // Did not find any layers that have frame rate.
-    return false;
-}
-
 } // namespace android::scheduler::impl
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index f217134..bef04e9 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -25,6 +25,8 @@
 #include <utility>
 #include <vector>
 
+#include "RefreshRateConfigs.h"
+
 namespace android {
 
 class Layer;
@@ -33,29 +35,32 @@
 namespace scheduler {
 
 class LayerHistoryTest;
+class LayerHistoryTestV2;
 class LayerInfo;
+class LayerInfoV2;
 
 class LayerHistory {
 public:
+    using LayerVoteType = RefreshRateConfigs::LayerVoteType;
+
     virtual ~LayerHistory() = default;
 
     // Layers are unregistered when the weak reference expires.
-    virtual void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate) = 0;
+    virtual void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate,
+                               LayerVoteType type) = 0;
+
+    // Sets the display size. Client is responsible for synchronization.
+    virtual void setDisplayArea(uint32_t displayArea) = 0;
 
     // Marks the layer as active, and records the given state to its history.
     virtual void record(Layer*, nsecs_t presentTime, nsecs_t now) = 0;
 
-    struct Summary {
-        float maxRefreshRate; // Maximum refresh rate among recently active layers.
-    };
+    using Summary = std::vector<RefreshRateConfigs::LayerRequirement>;
 
     // Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
     virtual Summary summarize(nsecs_t now) = 0;
 
     virtual void clear() = 0;
-
-    // Checks whether any of the active layers have a desired frame rate bit set on them.
-    virtual bool hasClientSpecifiedFrameRate() = 0;
 };
 
 namespace impl {
@@ -68,7 +73,10 @@
     virtual ~LayerHistory();
 
     // Layers are unregistered when the weak reference expires.
-    void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate) override;
+    void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate,
+                       LayerVoteType type) override;
+
+    void setDisplayArea(uint32_t /*displayArea*/) override {}
 
     // Marks the layer as active, and records the given state to its history.
     void record(Layer*, nsecs_t presentTime, nsecs_t now) override;
@@ -78,10 +86,6 @@
 
     void clear() override;
 
-    // Traverses all active layers and checks whether any of them have a desired frame
-    // rate bit set on them.
-    bool hasClientSpecifiedFrameRate() override;
-
 private:
     friend class android::scheduler::LayerHistoryTest;
     friend TestableScheduler;
@@ -91,7 +95,7 @@
 
     struct ActiveLayers {
         LayerInfos& infos;
-        const size_t index;
+        const long index;
 
         auto begin() { return infos.begin(); }
         auto end() { return begin() + index; }
@@ -109,8 +113,66 @@
     // Partitioned such that active layers precede inactive layers. For fast lookup, the few active
     // layers are at the front, and weak pointers are stored in contiguous memory to hit the cache.
     LayerInfos mLayerInfos GUARDED_BY(mLock);
+    long mActiveLayersEnd GUARDED_BY(mLock) = 0;
+
+    // Whether to emit systrace output and debug logs.
+    const bool mTraceEnabled;
+
+    // Whether to use priority sent from WindowManager to determine the relevancy of the layer.
+    const bool mUseFrameRatePriority;
+};
+
+class LayerHistoryV2 : public android::scheduler::LayerHistory {
+public:
+    LayerHistoryV2();
+    virtual ~LayerHistoryV2();
+
+    // Layers are unregistered when the weak reference expires.
+    void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate,
+                       LayerVoteType type) override;
+
+    // Sets the display size. Client is responsible for synchronization.
+    void setDisplayArea(uint32_t displayArea) override { mDisplayArea = displayArea; }
+
+    // Marks the layer as active, and records the given state to its history.
+    void record(Layer*, nsecs_t presentTime, nsecs_t now) override;
+
+    // Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
+    android::scheduler::LayerHistory::Summary summarize(nsecs_t /*now*/) override;
+
+    void clear() override;
+
+private:
+    friend android::scheduler::LayerHistoryTestV2;
+    friend TestableScheduler;
+
+    using LayerPair = std::pair<wp<Layer>, std::unique_ptr<LayerInfoV2>>;
+    using LayerInfos = std::vector<LayerPair>;
+
+    struct ActiveLayers {
+        LayerInfos& infos;
+        const size_t index;
+
+        auto begin() { return infos.begin(); }
+        auto end() { return begin() + static_cast<long>(index); }
+    };
+
+    ActiveLayers activeLayers() REQUIRES(mLock) { return {mLayerInfos, mActiveLayersEnd}; }
+
+    // Iterates over layers in a single pass, swapping pairs such that active layers precede
+    // inactive layers, and inactive layers precede expired layers. Removes expired layers by
+    // truncating after inactive layers.
+    void partitionLayers(nsecs_t now) REQUIRES(mLock);
+
+    mutable std::mutex mLock;
+
+    // Partitioned such that active layers precede inactive layers. For fast lookup, the few active
+    // layers are at the front, and weak pointers are stored in contiguous memory to hit the cache.
+    LayerInfos mLayerInfos GUARDED_BY(mLock);
     size_t mActiveLayersEnd GUARDED_BY(mLock) = 0;
 
+    uint32_t mDisplayArea = 0;
+
     // Whether to emit systrace output and debug logs.
     const bool mTraceEnabled;
 
diff --git a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
new file mode 100644
index 0000000..884b46a
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LayerHistoryV2"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "LayerHistory.h"
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
+#include <cutils/properties.h>
+#include <utils/Log.h>
+#include <utils/Timers.h>
+#include <utils/Trace.h>
+
+#include <algorithm>
+#include <cmath>
+#include <string>
+#include <utility>
+
+#include "../Layer.h"
+#include "SchedulerUtils.h"
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
+#include "LayerInfoV2.h"
+
+namespace android::scheduler::impl {
+
+namespace {
+
+bool isLayerActive(const Layer& layer, const LayerInfoV2& info, nsecs_t threshold) {
+    if (layer.getFrameRate().has_value()) {
+        return layer.isVisible();
+    }
+    return layer.isVisible() && info.getLastUpdatedTime() >= threshold;
+}
+
+bool traceEnabled() {
+    return property_get_bool("debug.sf.layer_history_trace", false);
+}
+
+bool useFrameRatePriority() {
+    char value[PROPERTY_VALUE_MAX];
+    property_get("debug.sf.use_frame_rate_priority", value, "1");
+    return atoi(value);
+}
+
+void trace(const wp<Layer>& weak, LayerHistory::LayerVoteType type, int fps) {
+    const auto layer = weak.promote();
+    if (!layer) return;
+
+    const auto& name = layer->getName();
+    const auto noVoteTag = "LFPS NoVote " + name;
+    const auto heuristicVoteTag = "LFPS Heuristic " + name;
+    const auto explicitVoteTag = "LFPS Explicit " + name;
+    const auto minVoteTag = "LFPS Min " + name;
+    const auto maxVoteTag = "LFPS Max " + name;
+
+    ATRACE_INT(noVoteTag.c_str(), type == LayerHistory::LayerVoteType::NoVote ? 1 : 0);
+    ATRACE_INT(heuristicVoteTag.c_str(), type == LayerHistory::LayerVoteType::Heuristic ? fps : 0);
+    ATRACE_INT(explicitVoteTag.c_str(), type == LayerHistory::LayerVoteType::Explicit ? fps : 0);
+    ATRACE_INT(minVoteTag.c_str(), type == LayerHistory::LayerVoteType::Min ? 1 : 0);
+    ATRACE_INT(maxVoteTag.c_str(), type == LayerHistory::LayerVoteType::Max ? 1 : 0);
+
+    ALOGD("%s: %s @ %d Hz", __FUNCTION__, name.c_str(), fps);
+}
+
+} // namespace
+
+LayerHistoryV2::LayerHistoryV2()
+      : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {}
+LayerHistoryV2::~LayerHistoryV2() = default;
+
+void LayerHistoryV2::registerLayer(Layer* layer, float /*lowRefreshRate*/, float highRefreshRate,
+                                   LayerVoteType type) {
+    const nsecs_t highRefreshRatePeriod = static_cast<nsecs_t>(1e9f / highRefreshRate);
+    auto info = std::make_unique<LayerInfoV2>(highRefreshRatePeriod, type);
+    std::lock_guard lock(mLock);
+    mLayerInfos.emplace_back(layer, std::move(info));
+}
+
+void LayerHistoryV2::record(Layer* layer, nsecs_t presentTime, nsecs_t now) {
+    std::lock_guard lock(mLock);
+
+    const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),
+                                 [layer](const auto& pair) { return pair.first == layer; });
+    LOG_FATAL_IF(it == mLayerInfos.end(), "%s: unknown layer %p", __FUNCTION__, layer);
+
+    const auto& info = it->second;
+    info->setLastPresentTime(presentTime, now);
+
+    // Activate layer if inactive.
+    if (const auto end = activeLayers().end(); it >= end) {
+        std::iter_swap(it, end);
+        mActiveLayersEnd++;
+    }
+}
+
+LayerHistoryV2::Summary LayerHistoryV2::summarize(nsecs_t now) {
+    LayerHistory::Summary summary;
+
+    std::lock_guard lock(mLock);
+
+    partitionLayers(now);
+
+    for (const auto& [layer, info] : activeLayers()) {
+        const auto strong = layer.promote();
+        if (!strong) {
+            continue;
+        }
+
+        const bool recent = info->isRecentlyActive(now);
+        if (recent) {
+            const auto [type, refreshRate] = info->getRefreshRate(now);
+            // Skip NoVote layer as those don't have any requirements
+            if (type == LayerHistory::LayerVoteType::NoVote) {
+                continue;
+            }
+
+            // Compute the layer's position on the screen
+            const Rect bounds = Rect(strong->getBounds());
+            const ui::Transform transform = strong->getTransform();
+            constexpr bool roundOutwards = true;
+            Rect transformed = transform.transform(bounds, roundOutwards);
+
+            const float layerArea = transformed.getWidth() * transformed.getHeight();
+            float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0f;
+            summary.push_back({strong->getName(), type, refreshRate, weight});
+
+            if (CC_UNLIKELY(mTraceEnabled)) {
+                trace(layer, type, static_cast<int>(std::round(refreshRate)));
+            }
+        } else if (CC_UNLIKELY(mTraceEnabled)) {
+            trace(layer, LayerHistory::LayerVoteType::NoVote, 0);
+        }
+    }
+
+    return summary;
+}
+
+void LayerHistoryV2::partitionLayers(nsecs_t now) {
+    const nsecs_t threshold = getActiveLayerThreshold(now);
+
+    // Collect expired and inactive layers after active layers.
+    size_t i = 0;
+    while (i < mActiveLayersEnd) {
+        auto& [weak, info] = mLayerInfos[i];
+        if (const auto layer = weak.promote(); layer && isLayerActive(*layer, *info, threshold)) {
+            i++;
+            // Set layer vote if set
+            const auto frameRate = layer->getFrameRate();
+            if (frameRate.has_value()) {
+                if (*frameRate == Layer::FRAME_RATE_NO_VOTE) {
+                    info->setLayerVote(LayerVoteType::NoVote, 0.f);
+                } else {
+                    info->setLayerVote(LayerVoteType::Explicit, *frameRate);
+                }
+            } else {
+                info->resetLayerVote();
+            }
+            continue;
+        }
+
+        if (CC_UNLIKELY(mTraceEnabled)) {
+            trace(weak, LayerHistory::LayerVoteType::NoVote, 0);
+        }
+
+        info->clearHistory();
+        std::swap(mLayerInfos[i], mLayerInfos[--mActiveLayersEnd]);
+    }
+
+    // Collect expired layers after inactive layers.
+    size_t end = mLayerInfos.size();
+    while (i < end) {
+        if (mLayerInfos[i].first.promote()) {
+            i++;
+        } else {
+            std::swap(mLayerInfos[i], mLayerInfos[--end]);
+        }
+    }
+
+    mLayerInfos.erase(mLayerInfos.begin() + static_cast<long>(end), mLayerInfos.end());
+}
+
+void LayerHistoryV2::clear() {
+    std::lock_guard lock(mLock);
+
+    for (const auto& [layer, info] : activeLayers()) {
+        info->clearHistory();
+    }
+
+    mActiveLayersEnd = 0;
+}
+
+} // namespace android::scheduler::impl
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
new file mode 100644
index 0000000..d94d758
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2020 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.
+ */
+
+// #define LOG_NDEBUG 0
+
+#include "LayerInfoV2.h"
+
+#include <algorithm>
+#include <utility>
+
+#undef LOG_TAG
+#define LOG_TAG "LayerInfoV2"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+namespace android::scheduler {
+
+LayerInfoV2::LayerInfoV2(nsecs_t highRefreshRatePeriod, LayerHistory::LayerVoteType defaultVote)
+      : mHighRefreshRatePeriod(highRefreshRatePeriod),
+        mDefaultVote(defaultVote),
+        mLayerVote({defaultVote, 0.0f}) {}
+
+void LayerInfoV2::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now) {
+    lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));
+
+    mLastUpdatedTime = std::max(lastPresentTime, now);
+
+    FrameTimeData frameTime = {.presetTime = lastPresentTime, .queueTime = mLastUpdatedTime};
+
+    mFrameTimes.push_back(frameTime);
+    if (mFrameTimes.size() > HISTORY_SIZE) {
+        mFrameTimes.pop_front();
+    }
+}
+
+// Returns whether the earliest present time is within the active threshold.
+bool LayerInfoV2::isRecentlyActive(nsecs_t now) const {
+    if (mFrameTimes.empty()) {
+        return false;
+    }
+
+    return mFrameTimes.back().queueTime >= getActiveLayerThreshold(now);
+}
+
+bool LayerInfoV2::isFrequent(nsecs_t now) const {
+    // Assume layer is infrequent if too few present times have been recorded.
+    if (mFrameTimes.size() < FREQUENT_LAYER_WINDOW_SIZE) {
+        return true;
+    }
+
+    // Layer is frequent if the earliest value in the window of most recent present times is
+    // within threshold.
+    const auto it = mFrameTimes.end() - FREQUENT_LAYER_WINDOW_SIZE;
+    const nsecs_t threshold = now - MAX_FREQUENT_LAYER_PERIOD_NS.count();
+    return it->queueTime >= threshold;
+}
+
+bool LayerInfoV2::hasEnoughDataForHeuristic() const {
+    // The layer had to publish at least HISTORY_SIZE or HISTORY_TIME of updates
+    if (mFrameTimes.size() < HISTORY_SIZE &&
+        mFrameTimes.back().queueTime - mFrameTimes.front().queueTime < HISTORY_TIME.count()) {
+        return false;
+    }
+
+    return true;
+}
+
+std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible() {
+    static constexpr float MARGIN = 1.0f; // 1Hz
+
+    if (!hasEnoughDataForHeuristic()) {
+        ALOGV("Not enough data");
+        return std::nullopt;
+    }
+
+    // Calculate the refresh rate by finding the average delta between frames
+    nsecs_t totalPresentTimeDeltas = 0;
+    for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
+        // If there are no presentation timestamp provided we can't calculate the refresh rate
+        if (it->presetTime == 0 || (it + 1)->presetTime == 0) {
+            return std::nullopt;
+        }
+
+        totalPresentTimeDeltas +=
+                std::max(((it + 1)->presetTime - it->presetTime), mHighRefreshRatePeriod);
+    }
+    const float averageFrameTime =
+            static_cast<float>(totalPresentTimeDeltas) / (mFrameTimes.size() - 1);
+
+    // Now once we calculated the refresh rate we need to make sure that all the frames we captured
+    // are evenly distrubuted and we don't calculate the average across some burst of frames.
+
+    for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
+        const nsecs_t presentTimeDeltas =
+                std::max(((it + 1)->presetTime - it->presetTime), mHighRefreshRatePeriod);
+        if (std::abs(presentTimeDeltas - averageFrameTime) > 2 * averageFrameTime) {
+            return std::nullopt;
+        }
+    }
+
+    const auto refreshRate = 1e9f / averageFrameTime;
+    if (std::abs(refreshRate - mLastReportedRefreshRate) > MARGIN) {
+        mLastReportedRefreshRate = refreshRate;
+    }
+
+    ALOGV("Refresh rate: %.2f", mLastReportedRefreshRate);
+    return mLastReportedRefreshRate;
+}
+
+std::pair<LayerHistory::LayerVoteType, float> LayerInfoV2::getRefreshRate(nsecs_t now) {
+    if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
+        return {mLayerVote.type, mLayerVote.fps};
+    }
+
+    if (!isFrequent(now)) {
+        return {LayerHistory::LayerVoteType::Min, 0};
+    }
+
+    auto refreshRate = calculateRefreshRateIfPossible();
+    if (refreshRate.has_value()) {
+        return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
+    }
+
+    return {LayerHistory::LayerVoteType::Max, 0};
+}
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.h b/services/surfaceflinger/Scheduler/LayerInfoV2.h
new file mode 100644
index 0000000..564f05e
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/LayerInfoV2.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2020 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 <utils/Timers.h>
+
+#include <chrono>
+#include <deque>
+
+#include "LayerHistory.h"
+#include "RefreshRateConfigs.h"
+#include "SchedulerUtils.h"
+
+namespace android {
+
+class Layer;
+
+namespace scheduler {
+
+using namespace std::chrono_literals;
+
+// Maximum period between presents for a layer to be considered active.
+constexpr std::chrono::nanoseconds MAX_ACTIVE_LAYER_PERIOD_NS = 1200ms;
+
+// Earliest present time for a layer to be considered active.
+constexpr nsecs_t getActiveLayerThreshold(nsecs_t now) {
+    return now - MAX_ACTIVE_LAYER_PERIOD_NS.count();
+}
+
+// Stores history of present times and refresh rates for a layer.
+class LayerInfoV2 {
+    // Layer is considered frequent if the earliest value in the window of most recent present times
+    // is within a threshold. If a layer is infrequent, its average refresh rate is disregarded in
+    // favor of a low refresh rate.
+    static constexpr size_t FREQUENT_LAYER_WINDOW_SIZE = 3;
+    static constexpr std::chrono::nanoseconds MAX_FREQUENT_LAYER_PERIOD_NS = 250ms;
+
+    friend class LayerHistoryTestV2;
+
+public:
+    LayerInfoV2(nsecs_t highRefreshRatePeriod, LayerHistory::LayerVoteType defaultVote);
+
+    LayerInfoV2(const LayerInfo&) = delete;
+    LayerInfoV2& operator=(const LayerInfoV2&) = delete;
+
+    // Records the last requested present time. It also stores information about when
+    // the layer was last updated. If the present time is farther in the future than the
+    // updated time, the updated time is the present time.
+    void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now);
+
+    bool isRecentlyActive(nsecs_t now) const;
+
+    // Sets an explicit layer vote. This usually comes directly from the application via
+    // ANativeWindow_setFrameRate API
+    void setLayerVote(LayerHistory::LayerVoteType type, float fps) { mLayerVote = {type, fps}; }
+
+    // Sets the default layer vote. This will be the layer vote after calling to resetLayerVote().
+    // This is used for layers that called to setLayerVote() and then removed the vote, so that the
+    // layer can go back to whatever vote it had before the app voted for it.
+    void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; }
+
+    // Resets the layer vote to its default.
+    void resetLayerVote() { mLayerVote = {mDefaultVote, 0.0f}; }
+
+    std::pair<LayerHistory::LayerVoteType, float> getRefreshRate(nsecs_t now);
+
+    // Return the last updated time. If the present time is farther in the future than the
+    // updated time, the updated time is the present time.
+    nsecs_t getLastUpdatedTime() const { return mLastUpdatedTime; }
+
+    void clearHistory() { mFrameTimes.clear(); }
+
+private:
+    bool isFrequent(nsecs_t now) const;
+    bool hasEnoughDataForHeuristic() const;
+    std::optional<float> calculateRefreshRateIfPossible();
+
+    // Used for sanitizing the heuristic data
+    const nsecs_t mHighRefreshRatePeriod;
+    LayerHistory::LayerVoteType mDefaultVote;
+
+    nsecs_t mLastUpdatedTime = 0;
+
+    float mLastReportedRefreshRate = 0.0f;
+
+    // Holds information about the layer vote
+    struct {
+        LayerHistory::LayerVoteType type;
+        float fps;
+    } mLayerVote;
+
+    // Used to store the layer timestamps
+    struct FrameTimeData {
+        nsecs_t presetTime; // desiredPresentTime, if provided
+        nsecs_t queueTime;  // buffer queue time
+    };
+    std::deque<FrameTimeData> mFrameTimes;
+    static constexpr size_t HISTORY_SIZE = 90;
+    static constexpr std::chrono::nanoseconds HISTORY_TIME = 1s;
+};
+
+} // namespace scheduler
+} // namespace android
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 45d1f23..a8f8e0e 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -14,24 +14,51 @@
  * limitations under the License.
  */
 
+// #define LOG_NDEBUG 0
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 
-// #define LOG_NDEBUG 0
 #include "RefreshRateConfigs.h"
+#include <android-base/stringprintf.h>
+#include <utils/Trace.h>
+#include <chrono>
+#include <cmath>
+
+using namespace std::chrono_literals;
 
 namespace android::scheduler {
 
 using AllRefreshRatesMapType = RefreshRateConfigs::AllRefreshRatesMapType;
 using RefreshRate = RefreshRateConfigs::RefreshRate;
 
-// 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 RefreshRate& RefreshRateConfigs::getRefreshRateForContent(float contentFramerate) const {
+const RefreshRate& RefreshRateConfigs::getRefreshRateForContent(
+        const std::vector<LayerRequirement>& layers) const {
     std::lock_guard lock(mLock);
+    float contentFramerate = 0.0f;
+    float explicitContentFramerate = 0.0f;
+    for (const auto& layer : layers) {
+        if (layer.vote == LayerVoteType::Explicit) {
+            if (layer.desiredRefreshRate > explicitContentFramerate) {
+                explicitContentFramerate = layer.desiredRefreshRate;
+            }
+        } else {
+            if (layer.desiredRefreshRate > contentFramerate) {
+                contentFramerate = layer.desiredRefreshRate;
+            }
+        }
+    }
+
+    if (explicitContentFramerate != 0.0f) {
+        contentFramerate = explicitContentFramerate;
+    } else if (contentFramerate == 0.0f) {
+        contentFramerate = mMaxSupportedRefreshRate->fps;
+    }
+    contentFramerate = std::round(contentFramerate);
+    ATRACE_INT("ContentFPS", contentFramerate);
+
     // Find the appropriate refresh rate with minimal error
     auto iter = min_element(mAvailableRefreshRates.cbegin(), mAvailableRefreshRates.cend(),
                             [contentFramerate](const auto& lhs, const auto& rhs) -> bool {
@@ -60,6 +87,113 @@
     return *bestSoFar;
 }
 
+const RefreshRate& RefreshRateConfigs::getRefreshRateForContentV2(
+        const std::vector<LayerRequirement>& layers) const {
+    constexpr nsecs_t MARGIN = std::chrono::nanoseconds(800us).count();
+    ATRACE_CALL();
+    ALOGV("getRefreshRateForContent %zu layers", layers.size());
+
+    std::lock_guard lock(mLock);
+
+    int noVoteLayers = 0;
+    int minVoteLayers = 0;
+    int maxVoteLayers = 0;
+    int explicitVoteLayers = 0;
+    for (const auto& layer : layers) {
+        if (layer.vote == LayerVoteType::NoVote)
+            noVoteLayers++;
+        else if (layer.vote == LayerVoteType::Min)
+            minVoteLayers++;
+        else if (layer.vote == LayerVoteType::Max)
+            maxVoteLayers++;
+        else if (layer.vote == LayerVoteType::Explicit)
+            explicitVoteLayers++;
+    }
+
+    // Only if all layers want Min we should return Min
+    if (noVoteLayers + minVoteLayers == layers.size()) {
+        return *mAvailableRefreshRates.front();
+    }
+
+    // If we have some Max layers and no Explicit we should return Max
+    if (maxVoteLayers > 0 && explicitVoteLayers == 0) {
+        return *mAvailableRefreshRates.back();
+    }
+
+    // Find the best refresh rate based on score
+    std::vector<std::pair<const RefreshRate*, float>> scores;
+    scores.reserve(mAvailableRefreshRates.size());
+
+    for (const auto refreshRate : mAvailableRefreshRates) {
+        scores.emplace_back(refreshRate, 0.0f);
+    }
+
+    for (const auto& layer : layers) {
+        if (layer.vote == LayerVoteType::NoVote || layer.vote == LayerVoteType::Min ||
+            layer.vote == LayerVoteType::Max) {
+            continue;
+        }
+
+        // If we have Explicit layers, ignore the Huristic ones
+        if (explicitVoteLayers > 0 && layer.vote == LayerVoteType::Heuristic) {
+            continue;
+        }
+
+        for (auto& [refreshRate, overallScore] : scores) {
+            const auto displayPeriod = refreshRate->vsyncPeriod;
+            const auto layerPeriod = 1e9f / layer.desiredRefreshRate;
+
+            // Calculate how many display vsyncs we need to present a single frame for this layer
+            auto [displayFramesQuot, displayFramesRem] = std::div(layerPeriod, displayPeriod);
+            if (displayFramesRem <= MARGIN ||
+                std::abs(displayFramesRem - displayPeriod) <= MARGIN) {
+                displayFramesQuot++;
+                displayFramesRem = 0;
+            }
+
+            float layerScore;
+            if (displayFramesRem == 0) {
+                // Layer desired refresh rate matches the display rate.
+                layerScore = layer.weight * 1.0f;
+            } else if (displayFramesQuot == 0) {
+                // Layer desired refresh rate is higher the display rate.
+                layerScore = layer.weight * layerPeriod / displayPeriod;
+            } else {
+                // Layer desired refresh rate is lower the display rate. Check how well it fits the
+                // cadence
+                auto diff = std::abs(displayFramesRem - (displayPeriod - displayFramesRem));
+                int iter = 2;
+                static constexpr size_t MAX_ITERATOR = 10; // Stop calculating when score < 0.1
+                while (diff > MARGIN && iter < MAX_ITERATOR) {
+                    diff = diff - (displayPeriod - diff);
+                    iter++;
+                }
+
+                layerScore = layer.weight * 1.0f / iter;
+            }
+
+            ALOGV("%s (weight %.2f) %.2fHz gives %s score of %.2f", layer.name.c_str(),
+                  layer.weight, 1e9f / layerPeriod, refreshRate->name.c_str(), layerScore);
+            overallScore += layerScore;
+        }
+    }
+
+    float max = 0;
+    const RefreshRate* bestRefreshRate = nullptr;
+    for (const auto [refreshRate, score] : scores) {
+        ALOGV("%s scores %.2f", refreshRate->name.c_str(), score);
+
+        ATRACE_INT(refreshRate->name.c_str(), std::round(score * 100));
+
+        if (score > max) {
+            max = score;
+            bestRefreshRate = refreshRate;
+        }
+    }
+
+    return bestRefreshRate == nullptr ? *mCurrentRefreshRate : *bestRefreshRate;
+}
+
 const AllRefreshRatesMapType& RefreshRateConfigs::getAllRefreshRates() const {
     return mRefreshRates;
 }
@@ -180,14 +314,21 @@
 void RefreshRateConfigs::constructAvailableRefreshRates() {
     // Filter configs based on current policy and sort based on vsync period
     HwcConfigGroupType group = mRefreshRates.at(mDefaultConfig).configGroup;
-    ALOGV("constructRefreshRateMap: default %d group %d min %.2f max %.2f", mDefaultConfig.value(),
-          group.value(), mMinRefreshRateFps, mMaxRefreshRateFps);
+    ALOGV("constructAvailableRefreshRates: default %d group %d min %.2f max %.2f",
+          mDefaultConfig.value(), group.value(), mMinRefreshRateFps, mMaxRefreshRateFps);
     getSortedRefreshRateList(
             [&](const RefreshRate& refreshRate) REQUIRES(mLock) {
                 return refreshRate.configGroup == group &&
                         refreshRate.inPolicy(mMinRefreshRateFps, mMaxRefreshRateFps);
             },
             &mAvailableRefreshRates);
+
+    std::string availableRefreshRates;
+    for (const auto& refreshRate : mAvailableRefreshRates) {
+        base::StringAppendF(&availableRefreshRates, "%s ", refreshRate->name.c_str());
+    }
+
+    ALOGV("Available refresh rates: %s", availableRefreshRates.c_str());
     LOG_ALWAYS_FATAL_IF(mAvailableRefreshRates.empty(),
                         "No compatible display configs for default=%d min=%.0f max=%.0f",
                         mDefaultConfig.value(), mMinRefreshRateFps, mMaxRefreshRateFps);
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 0c3369a..80d42cc 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -97,8 +97,39 @@
     // Returns true if this device is doing refresh rate switching. This won't change at runtime.
     bool refreshRateSwitchingSupported() const { return mRefreshRateSwitching; }
 
+    // Describes the different options the layer voted for refresh rate
+    enum class LayerVoteType {
+        NoVote,    // Doesn't care about the refresh rate
+        Min,       // Minimal refresh rate available
+        Max,       // Maximal refresh rate available
+        Heuristic, // Specific refresh rate that was calculated by platform using a heuristic
+        Explicit,  // Specific refresh rate that was provided by the app
+    };
+
+    // Captures the layer requirements for a refresh rate. This will be used to determine the
+    // display refresh rate.
+    struct LayerRequirement {
+        std::string name;         // Layer's name. Used for debugging purposes.
+        LayerVoteType vote;       // Layer vote type.
+        float desiredRefreshRate; // Layer's desired refresh rate, if applicable.
+        float weight; // Layer's weight in the range of [0, 1]. The higher the weight the more
+                      // impact this layer would have on choosing the refresh rate.
+
+        bool operator==(const LayerRequirement& other) const {
+            return name == other.name && vote == other.vote &&
+                    desiredRefreshRate == other.desiredRefreshRate && weight == other.weight;
+        }
+
+        bool operator!=(const LayerRequirement& other) const { return !(*this == other); }
+    };
+
     // Returns all available refresh rates according to the current policy.
-    const RefreshRate& getRefreshRateForContent(float contentFramerate) const EXCLUDES(mLock);
+    const RefreshRate& getRefreshRateForContent(const std::vector<LayerRequirement>& layers) const
+            EXCLUDES(mLock);
+
+    // Returns all available refresh rates according to the current policy.
+    const RefreshRate& getRefreshRateForContentV2(const std::vector<LayerRequirement>& layers) const
+            EXCLUDES(mLock);
 
     // Returns all the refresh rates supported by the device. This won't change at runtime.
     const AllRefreshRatesMapType& getAllRefreshRates() const EXCLUDES(mLock);
diff --git a/services/surfaceflinger/Scheduler/RefreshRateStats.h b/services/surfaceflinger/Scheduler/RefreshRateStats.h
index a384dbe..e44cd52 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateStats.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateStats.h
@@ -114,7 +114,8 @@
                 mConfigModesTotalTime[mCurrentConfigMode] = 0;
             }
             mConfigModesTotalTime[mCurrentConfigMode] += timeElapsedMs;
-            fps = mRefreshRateConfigs.getRefreshRateFromConfigId(mCurrentConfigMode).fps;
+            fps = static_cast<uint32_t>(std::round(
+                    mRefreshRateConfigs.getRefreshRateFromConfigId(mCurrentConfigMode).fps));
         } else {
             mScreenOffTime += timeElapsedMs;
         }
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 0b645c4..6a437a2 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -103,16 +103,21 @@
 
 Scheduler::Scheduler(impl::EventControlThread::SetVSyncEnabledFunction function,
                      const scheduler::RefreshRateConfigs& refreshRateConfig,
-                     ISchedulerCallback& schedulerCallback)
+                     ISchedulerCallback& schedulerCallback, bool useContentDetectionV2)
       : mPrimaryDispSync(createDispSync()),
         mEventControlThread(new impl::EventControlThread(std::move(function))),
         mSupportKernelTimer(sysprop::support_kernel_idle_timer(false)),
         mSchedulerCallback(schedulerCallback),
-        mRefreshRateConfigs(refreshRateConfig) {
+        mRefreshRateConfigs(refreshRateConfig),
+        mUseContentDetectionV2(useContentDetectionV2) {
     using namespace sysprop;
 
     if (property_get_bool("debug.sf.use_smart_90_for_video", 0) || use_smart_90_for_video(false)) {
-        mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
+        if (mUseContentDetectionV2) {
+            mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>();
+        } else {
+            mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
+        }
     }
 
     const int setIdleTimerMs = property_get_int32("debug.sf.set_idle_timer_ms", 0);
@@ -120,7 +125,6 @@
     if (const auto millis = setIdleTimerMs ? setIdleTimerMs : set_idle_timer_ms(0); millis > 0) {
         const auto callback = mSupportKernelTimer ? &Scheduler::kernelIdleTimerCallback
                                                   : &Scheduler::idleTimerCallback;
-
         mIdleTimer.emplace(
                 std::chrono::milliseconds(millis),
                 [this, callback] { std::invoke(callback, this, TimerState::Reset); },
@@ -149,12 +153,13 @@
 Scheduler::Scheduler(std::unique_ptr<DispSync> primaryDispSync,
                      std::unique_ptr<EventControlThread> eventControlThread,
                      const scheduler::RefreshRateConfigs& configs,
-                     ISchedulerCallback& schedulerCallback)
+                     ISchedulerCallback& schedulerCallback, bool useContentDetectionV2)
       : mPrimaryDispSync(std::move(primaryDispSync)),
         mEventControlThread(std::move(eventControlThread)),
         mSupportKernelTimer(false),
         mSchedulerCallback(schedulerCallback),
-        mRefreshRateConfigs(configs) {}
+        mRefreshRateConfigs(configs),
+        mUseContentDetectionV2(useContentDetectionV2) {}
 
 Scheduler::~Scheduler() {
     // Ensure the OneShotTimer threads are joined before we start destroying state.
@@ -375,12 +380,42 @@
 void Scheduler::registerLayer(Layer* layer) {
     if (!mLayerHistory) return;
 
-    const auto lowFps = mRefreshRateConfigs.getMinRefreshRate().fps;
-    const auto highFps = layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER
-            ? lowFps
-            : mRefreshRateConfigs.getMaxRefreshRate().fps;
+    if (!mUseContentDetectionV2) {
+        const auto lowFps = mRefreshRateConfigs.getMinRefreshRate().fps;
+        const auto highFps = layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER
+                ? lowFps
+                : mRefreshRateConfigs.getMaxRefreshRate().fps;
 
-    mLayerHistory->registerLayer(layer, lowFps, highFps);
+        mLayerHistory->registerLayer(layer, lowFps, highFps,
+                                     scheduler::LayerHistory::LayerVoteType::Heuristic);
+    } else {
+        if (layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER) {
+            mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().fps,
+                                         mRefreshRateConfigs.getMaxRefreshRate().fps,
+                                         scheduler::LayerHistory::LayerVoteType::Min);
+        } else if (layer->getWindowType() == InputWindowInfo::TYPE_STATUS_BAR) {
+            mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().fps,
+                                         mRefreshRateConfigs.getMaxRefreshRate().fps,
+                                         scheduler::LayerHistory::LayerVoteType::NoVote);
+        } else {
+            mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().fps,
+                                         mRefreshRateConfigs.getMaxRefreshRate().fps,
+                                         scheduler::LayerHistory::LayerVoteType::Heuristic);
+        }
+
+        // TODO(146935143): Simulate youtube app vote. This should be removed once youtube calls the
+        // API to set desired rate
+        {
+            const auto vote = property_get_int32("experimental.sf.force_youtube_vote", 0);
+            if (vote != 0 &&
+                layer->getName() ==
+                        "SurfaceView - "
+                        "com.google.android.youtube/"
+                        "com.google.android.apps.youtube.app.WatchWhileActivity#0") {
+                layer->setFrameRate(vote);
+            }
+        }
+    }
 }
 
 void Scheduler::recordLayerHistory(Layer* layer, nsecs_t presentTime) {
@@ -392,27 +427,27 @@
 void Scheduler::chooseRefreshRateForContent() {
     if (!mLayerHistory) return;
 
-    auto [refreshRate] = mLayerHistory->summarize(systemTime());
-    const uint32_t refreshRateRound = std::round(refreshRate);
+    ATRACE_CALL();
+
+    scheduler::LayerHistory::Summary summary = mLayerHistory->summarize(systemTime());
     HwcConfigIndexType newConfigId;
     {
         std::lock_guard<std::mutex> lock(mFeatureStateLock);
-        if (mFeatures.contentRefreshRate == refreshRateRound) {
+        if (mFeatures.contentRequirements == summary) {
             return;
         }
-        mFeatures.contentRefreshRate = refreshRateRound;
-        ATRACE_INT("ContentFPS", refreshRateRound);
-
+        mFeatures.contentRequirements = summary;
         mFeatures.contentDetection =
-                refreshRateRound > 0 ? ContentDetectionState::On : ContentDetectionState::Off;
+                !summary.empty() ? ContentDetectionState::On : ContentDetectionState::Off;
+
         newConfigId = calculateRefreshRateType();
         if (mFeatures.configId == newConfigId) {
             return;
         }
         mFeatures.configId = newConfigId;
-    };
-    auto newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
-    mSchedulerCallback.changeRefreshRate(newRefreshRate, ConfigEvent::Changed);
+        auto newRefreshRate = mRefreshRateConfigs.getRefreshRateFromConfigId(newConfigId);
+        mSchedulerCallback.changeRefreshRate(newRefreshRate, ConfigEvent::Changed);
+    }
 }
 
 void Scheduler::resetIdleTimer() {
@@ -422,16 +457,17 @@
 }
 
 void Scheduler::notifyTouchEvent() {
+    if (!mTouchTimer) return;
+
     // Touch event will boost the refresh rate to performance.
     // Clear Layer History to get fresh FPS detection.
     // NOTE: Instead of checking all the layers, we should be checking the layer
     // that is currently on top. b/142507166 will give us this capability.
-    if (mLayerHistory && !mLayerHistory->hasClientSpecifiedFrameRate()) {
+    std::lock_guard<std::mutex> lock(mFeatureStateLock);
+    if (mLayerHistory && !layerHistoryHasClientSpecifiedFrameRate()) {
         mLayerHistory->clear();
 
-        if (mTouchTimer) {
-            mTouchTimer->reset();
-        }
+        mTouchTimer->reset();
 
         if (mSupportKernelTimer && mIdleTimer) {
             mIdleTimer->reset();
@@ -530,6 +566,16 @@
     mSchedulerCallback.changeRefreshRate(newRefreshRate, event);
 }
 
+bool Scheduler::layerHistoryHasClientSpecifiedFrameRate() {
+    for (const auto& layer : mFeatures.contentRequirements) {
+        if (layer.vote == scheduler::RefreshRateConfigs::LayerVoteType::Explicit) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 HwcConfigIndexType Scheduler::calculateRefreshRateType() {
     if (!mRefreshRateConfigs.refreshRateSwitchingSupported()) {
         return mRefreshRateConfigs.getCurrentRefreshRate().configId;
@@ -538,7 +584,7 @@
     // If the layer history doesn't have the frame rate specified, use the old path. NOTE:
     // if we remove the kernel idle timer, and use our internal idle timer, this code will have to
     // be refactored.
-    if (!mLayerHistory->hasClientSpecifiedFrameRate()) {
+    if (!layerHistoryHasClientSpecifiedFrameRate()) {
         // 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 ||
@@ -555,17 +601,26 @@
         if (mFeatures.idleTimer == TimerState::Expired) {
             return mRefreshRateConfigs.getMinRefreshRateByPolicy().configId;
         }
+    }
 
+    if (!mUseContentDetectionV2) {
         // If content detection is off we choose performance as we don't know the content fps
         if (mFeatures.contentDetection == ContentDetectionState::Off) {
             return mRefreshRateConfigs.getMaxRefreshRateByPolicy().configId;
         }
+
+        // Content detection is on, find the appropriate refresh rate with minimal error
+        return mRefreshRateConfigs.getRefreshRateForContent(mFeatures.contentRequirements).configId;
     }
 
     // Content detection is on, find the appropriate refresh rate with minimal error
-    return mRefreshRateConfigs
-            .getRefreshRateForContent(static_cast<float>(mFeatures.contentRefreshRate))
-            .configId;
+    if (mFeatures.contentDetection == ContentDetectionState::On) {
+        return mRefreshRateConfigs.getRefreshRateForContentV2(mFeatures.contentRequirements)
+                .configId;
+    }
+
+    // There are no signals for refresh rate, just leave it as is
+    return mRefreshRateConfigs.getCurrentRefreshRate().configId;
 }
 
 std::optional<HwcConfigIndexType> Scheduler::getPreferredConfigId() {
@@ -606,6 +661,12 @@
     }
 }
 
+void Scheduler::onPrimaryDisplayAreaChanged(uint32_t displayArea) {
+    if (mLayerHistory) {
+        mLayerHistory->setDisplayArea(displayArea);
+    }
+}
+
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index c6430c3..2987424 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -58,7 +58,8 @@
     enum class TransactionStart { EARLY, NORMAL };
 
     Scheduler(impl::EventControlThread::SetVSyncEnabledFunction,
-              const scheduler::RefreshRateConfigs&, ISchedulerCallback& schedulerCallback);
+              const scheduler::RefreshRateConfigs&, ISchedulerCallback& schedulerCallback,
+              bool useContentDetectionV2);
 
     virtual ~Scheduler();
 
@@ -136,6 +137,9 @@
     // Notifies the scheduler when the display was refreshed
     void onDisplayRefreshed(nsecs_t timestamp);
 
+    // Notifies the scheduler when the display size has changed. Called from SF's main thread
+    void onPrimaryDisplayAreaChanged(uint32_t displayArea);
+
 private:
     friend class TestableScheduler;
 
@@ -147,7 +151,8 @@
 
     // Used by tests to inject mocks.
     Scheduler(std::unique_ptr<DispSync>, std::unique_ptr<EventControlThread>,
-              const scheduler::RefreshRateConfigs&, ISchedulerCallback& schedulerCallback);
+              const scheduler::RefreshRateConfigs&, ISchedulerCallback& schedulerCallback,
+              bool useContentDetectionV2);
 
     std::unique_ptr<VSyncSource> makePrimaryDispSyncSource(const char* name, nsecs_t phaseOffsetNs);
 
@@ -170,6 +175,8 @@
 
     HwcConfigIndexType calculateRefreshRateType() REQUIRES(mFeatureStateLock);
 
+    bool layerHistoryHasClientSpecifiedFrameRate() REQUIRES(mFeatureStateLock);
+
     // Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection.
     struct Connection {
         sp<EventThreadConnection> connection;
@@ -218,7 +225,7 @@
         TimerState displayPowerTimer = TimerState::Expired;
 
         std::optional<HwcConfigIndexType> configId;
-        uint32_t contentRefreshRate = 0;
+        scheduler::LayerHistory::Summary contentRequirements;
 
         bool isDisplayPowerStateNormal = true;
     } mFeatures GUARDED_BY(mFeatureStateLock);
@@ -229,6 +236,8 @@
     std::optional<HWC2::VsyncPeriodChangeTimeline> mLastVsyncPeriodChangeTimeline
             GUARDED_BY(mVsyncTimelineLock);
     static constexpr std::chrono::nanoseconds MAX_VSYNC_APPLIED_TIME = 200ms;
+
+    const bool mUseContentDetectionV2;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index d716c54..29d281c 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -543,6 +543,11 @@
             const auto& performanceRefreshRate = mRefreshRateConfigs->getMaxRefreshRateByPolicy();
             changeRefreshRateLocked(performanceRefreshRate, Scheduler::ConfigEvent::None);
         }
+
+        if (property_get_bool("sf.debug.show_refresh_rate_overlay", false)) {
+            mRefreshRateOverlay = std::make_unique<RefreshRateOverlay>(*this);
+            mRefreshRateOverlay->changeRefreshRate(mRefreshRateConfigs->getCurrentRefreshRate());
+        }
     }));
 }
 
@@ -1782,7 +1787,13 @@
 
             // Layers need to get updated (in the previous line) before we can use them for
             // choosing the refresh rate.
-            mScheduler->chooseRefreshRateForContent();
+            // Hold mStateLock as chooseRefreshRateForContent promotes wp<Layer> to sp<Layer>
+            // and may eventually call to ~Layer() if it holds the last reference
+            {
+                Mutex::Autolock _l(mStateLock);
+                mScheduler->chooseRefreshRateForContent();
+            }
+
             if (performSetActiveConfig()) {
                 break;
             }
@@ -2391,6 +2402,9 @@
                     }
                     if (state.width != draw[i].width || state.height != draw[i].height) {
                         display->setDisplaySize(state.width, state.height);
+                        if (display->isPrimary()) {
+                            mScheduler->onPrimaryDisplayAreaChanged(state.width * state.height);
+                        }
                     }
                 }
             }
@@ -2463,6 +2477,12 @@
                         LOG_ALWAYS_FATAL_IF(!displayId);
                         dispatchDisplayHotplugEvent(displayId->value, true);
                     }
+
+                    const auto displayDevice = mDisplays[displayToken];
+                    if (displayDevice->isPrimary()) {
+                        mScheduler->onPrimaryDisplayAreaChanged(displayDevice->getWidth() *
+                                                                displayDevice->getHeight());
+                    }
                 }
             }
         }
@@ -2660,8 +2680,12 @@
 }
 
 void SurfaceFlinger::changeRefreshRate(const RefreshRate& refreshRate,
-                                       Scheduler::ConfigEvent event) {
-    Mutex::Autolock lock(mStateLock);
+                                       Scheduler::ConfigEvent event) NO_THREAD_SAFETY_ANALYSIS {
+    // If this is called from the main thread mStateLock must be locked before
+    // Currently the only way to call this function from the main thread is from
+    // Sheduler::chooseRefreshRateForContent
+
+    ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId);
     changeRefreshRateLocked(refreshRate, event);
 }
 
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
index e12d31a..45889a5 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
@@ -75,7 +75,9 @@
 std::unique_ptr<Scheduler> DefaultFactory::createScheduler(
         SetVSyncEnabled setVSyncEnabled, const scheduler::RefreshRateConfigs& configs,
         ISchedulerCallback& schedulerCallback) {
-    return std::make_unique<Scheduler>(std::move(setVSyncEnabled), configs, schedulerCallback);
+    return std::make_unique<Scheduler>(std::move(setVSyncEnabled), configs, schedulerCallback,
+                                       property_get_bool("debug.sf.use_content_detection_v2",
+                                                         false));
 }
 
 std::unique_ptr<SurfaceInterceptor> DefaultFactory::createSurfaceInterceptor(
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 1eaf2dd..d046f76 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -44,6 +44,7 @@
         "EventThreadTest.cpp",
         "OneShotTimerTest.cpp",
         "LayerHistoryTest.cpp",
+        "LayerHistoryTestV2.cpp",
         "LayerMetadataTest.cpp",
         "PhaseOffsetsTest.cpp",
         "SchedulerTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index ca51cb6..9ca1b70 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -73,7 +73,7 @@
                                                                         HI_FPS_PERIOD},
                                 },
                                 HwcConfigIndexType(0)};
-    TestableScheduler* const mScheduler{new TestableScheduler(mConfigs)};
+    TestableScheduler* const mScheduler{new TestableScheduler(mConfigs, false)};
     TestableSurfaceFlinger mFlinger;
 
     const nsecs_t mTime = systemTime();
@@ -85,53 +85,36 @@
     const auto layer = createLayer();
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameSelectionPriority()).WillRepeatedly(Return(1));
+    EXPECT_CALL(*layer, getFrameRate()).WillRepeatedly(Return(std::nullopt));
 
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
 
-    // 0 FPS is returned if no layers are active.
-    EXPECT_FLOAT_EQ(0, history().summarize(mTime).maxRefreshRate);
+    // no layers are returned if no layers are active.
+    ASSERT_TRUE(history().summarize(mTime).empty());
     EXPECT_EQ(0, activeLayerCount());
 
-    // 0 FPS is returned if active layers have insufficient history.
+    // no layers are returned if active layers have insufficient history.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
         history().record(layer.get(), 0, mTime);
-        EXPECT_FLOAT_EQ(0, history().summarize(mTime).maxRefreshRate);
+        ASSERT_TRUE(history().summarize(mTime).empty());
         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, mTime);
-        EXPECT_FLOAT_EQ(HI_FPS, history().summarize(mTime).maxRefreshRate);
+        ASSERT_EQ(1, history().summarize(mTime).size());
+        EXPECT_FLOAT_EQ(HI_FPS, history().summarize(mTime)[0].desiredRefreshRate);
         EXPECT_EQ(1, activeLayerCount());
     }
 }
 
-TEST_F(LayerHistoryTest, oneHDRLayer) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameSelectionPriority()).WillRepeatedly(Return(1));
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    history().record(layer.get(), 0, mTime);
-    auto summary = history().summarize(mTime);
-    EXPECT_FLOAT_EQ(0, summary.maxRefreshRate);
-    EXPECT_EQ(1, activeLayerCount());
-
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
-
-    summary = history().summarize(mTime);
-    EXPECT_FLOAT_EQ(0, summary.maxRefreshRate);
-    EXPECT_EQ(0, activeLayerCount());
-}
-
 TEST_F(LayerHistoryTest, explicitTimestamp) {
     const auto layer = createLayer();
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameSelectionPriority()).WillRepeatedly(Return(1));
+    EXPECT_CALL(*layer, getFrameRate()).WillRepeatedly(Return(std::nullopt));
 
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
@@ -142,7 +125,8 @@
         time += LO_FPS_PERIOD;
     }
 
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(mTime).maxRefreshRate);
+    ASSERT_EQ(1, history().summarize(mTime).size());
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(mTime)[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 }
@@ -154,13 +138,15 @@
 
     EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer1, getFrameSelectionPriority()).WillRepeatedly(Return(1));
+    EXPECT_CALL(*layer1, getFrameRate()).WillRepeatedly(Return(std::nullopt));
 
     EXPECT_CALL(*layer2, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer2, getFrameSelectionPriority()).WillRepeatedly(Return(1));
+    EXPECT_CALL(*layer2, getFrameRate()).WillRepeatedly(Return(std::nullopt));
 
     EXPECT_CALL(*layer3, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer3, getFrameSelectionPriority()).WillRepeatedly(Return(1));
-
+    EXPECT_CALL(*layer3, getFrameRate()).WillRepeatedly(Return(std::nullopt));
     nsecs_t time = mTime;
 
     EXPECT_EQ(3, layerCount());
@@ -173,7 +159,8 @@
         time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
     }
 
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time).maxRefreshRate);
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
 
@@ -186,7 +173,9 @@
     // layer1 is still active but infrequent.
     history().record(layer1.get(), time, time);
 
-    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time).maxRefreshRate);
+    ASSERT_EQ(2, history().summarize(time).size());
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -197,7 +186,8 @@
         time += LO_FPS_PERIOD;
     }
 
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time).maxRefreshRate);
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -213,19 +203,24 @@
         time += HI_FPS_PERIOD;
     }
 
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time).maxRefreshRate);
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
 
     // layer3 becomes recently active.
     history().record(layer3.get(), time, time);
-    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time).maxRefreshRate);
+    ASSERT_EQ(2, history().summarize(time).size());
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
 
     // layer1 expires.
     layer1.clear();
-    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time).maxRefreshRate);
+    ASSERT_EQ(2, history().summarize(time).size());
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
     EXPECT_EQ(2, layerCount());
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
@@ -237,13 +232,14 @@
         time += LO_FPS_PERIOD;
     }
 
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time).maxRefreshRate);
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
     // layer2 expires.
     layer2.clear();
-    EXPECT_FLOAT_EQ(0, history().summarize(time).maxRefreshRate);
+    ASSERT_TRUE(history().summarize(time).empty());
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
@@ -254,14 +250,15 @@
         time += HI_FPS_PERIOD;
     }
 
-    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time).maxRefreshRate);
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[0].desiredRefreshRate);
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
     // layer3 expires.
     layer3.clear();
-    EXPECT_FLOAT_EQ(0, history().summarize(time).maxRefreshRate);
+    ASSERT_TRUE(history().summarize(time).empty());
     EXPECT_EQ(0, layerCount());
     EXPECT_EQ(0, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
new file mode 100644
index 0000000..11ace05
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
@@ -0,0 +1,418 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LayerHistoryTestV2"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <log/log.h>
+
+#include "Scheduler/LayerHistory.h"
+#include "Scheduler/LayerInfoV2.h"
+#include "TestableScheduler.h"
+#include "TestableSurfaceFlinger.h"
+#include "mock/MockLayer.h"
+
+using testing::_;
+using testing::Return;
+
+namespace android::scheduler {
+
+class LayerHistoryTestV2 : public testing::Test {
+protected:
+    static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfoV2::HISTORY_SIZE;
+    static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS = LayerInfoV2::MAX_FREQUENT_LAYER_PERIOD_NS;
+
+    static constexpr float LO_FPS = 30.f;
+    static constexpr auto LO_FPS_PERIOD = static_cast<nsecs_t>(1e9f / LO_FPS);
+
+    static constexpr float HI_FPS = 90.f;
+    static constexpr auto HI_FPS_PERIOD = static_cast<nsecs_t>(1e9f / HI_FPS);
+
+    LayerHistoryTestV2() { mFlinger.resetScheduler(mScheduler); }
+
+    impl::LayerHistoryV2& history() { return *mScheduler->mutableLayerHistoryV2(); }
+    const impl::LayerHistoryV2& history() const { return *mScheduler->mutableLayerHistoryV2(); }
+
+    size_t layerCount() const { return mScheduler->layerHistorySize(); }
+    size_t activeLayerCount() const NO_THREAD_SAFETY_ANALYSIS { return history().mActiveLayersEnd; }
+
+    auto frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
+        const auto& infos = history().mLayerInfos;
+        return std::count_if(infos.begin(),
+                             infos.begin() + static_cast<long>(history().mActiveLayersEnd),
+                             [now](const auto& pair) { return pair.second->isFrequent(now); });
+    }
+
+    void setLayerInfoVote(Layer* layer,
+                          LayerHistory::LayerVoteType vote) NO_THREAD_SAFETY_ANALYSIS {
+        for (auto& [weak, info] : history().mLayerInfos) {
+            if (auto strong = weak.promote(); strong && strong.get() == layer) {
+                info->setDefaultLayerVote(vote);
+                info->setLayerVote(vote, 0);
+                return;
+            }
+        }
+    }
+
+    auto createLayer() { return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger())); }
+
+    RefreshRateConfigs mConfigs{true,
+                                {
+                                        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, true)};
+    TestableSurfaceFlinger mFlinger;
+
+    const nsecs_t mTime = systemTime();
+};
+
+namespace {
+
+TEST_F(LayerHistoryTestV2, oneLayer) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRate()).WillRepeatedly(Return(std::nullopt));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    // No layers returned if no layers are active.
+    EXPECT_TRUE(history().summarize(mTime).empty());
+    EXPECT_EQ(0, activeLayerCount());
+
+    // Max returned if active layers have insufficient history.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
+        history().record(layer.get(), 0, mTime);
+        ASSERT_EQ(1, history().summarize(mTime).size());
+        EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(mTime)[0].vote);
+        EXPECT_EQ(1, activeLayerCount());
+    }
+
+    // Max is returned since we have enough history but there is no timestamp votes.
+    for (int i = 0; i < 10; i++) {
+        history().record(layer.get(), 0, mTime);
+        ASSERT_EQ(1, history().summarize(mTime).size());
+        EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(mTime)[0].vote);
+        EXPECT_EQ(1, activeLayerCount());
+    }
+}
+
+TEST_F(LayerHistoryTestV2, oneInvisibleLayer) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRate()).WillRepeatedly(Return(std::nullopt));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    history().record(layer.get(), 0, mTime);
+    auto summary = history().summarize(mTime);
+    ASSERT_EQ(1, history().summarize(mTime).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(mTime)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
+
+    summary = history().summarize(mTime);
+    EXPECT_TRUE(history().summarize(mTime).empty());
+    EXPECT_EQ(0, activeLayerCount());
+}
+
+TEST_F(LayerHistoryTestV2, explicitTimestamp) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRate()).WillRepeatedly(Return(std::nullopt));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = mTime;
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer.get(), time, time);
+        time += LO_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTestV2, oneLayerNoVote) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRate()).WillRepeatedly(Return(std::nullopt));
+
+    setLayerInfoVote(layer.get(), LayerHistory::LayerVoteType::NoVote);
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = mTime;
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer.get(), time, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_TRUE(history().summarize(time).empty());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became inactive
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_TRUE(history().summarize(time).empty());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTestV2, oneLayerMinVote) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRate()).WillRepeatedly(Return(std::nullopt));
+
+    setLayerInfoVote(layer.get(), LayerHistory::LayerVoteType::Min);
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = mTime;
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer.get(), time, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became inactive
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_TRUE(history().summarize(time).empty());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTestV2, oneLayerMaxVote) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRate()).WillRepeatedly(Return(std::nullopt));
+
+    setLayerInfoVote(layer.get(), LayerHistory::LayerVoteType::Max);
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = mTime;
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer.get(), time, time);
+        time += LO_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became inactive
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_TRUE(history().summarize(time).empty());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTestV2, oneLayerExplicitVote) {
+    auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRate()).WillRepeatedly(Return(73.4f));
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+
+    nsecs_t time = mTime;
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer.get(), time, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Explicit, history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became inactive
+    setLayerInfoVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_TRUE(history().summarize(time).empty());
+    // TODO: activeLayerCount() should be 0 but it is 1 since getFrameRate() returns a value > 0
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTestV2, multipleLayers) {
+    auto layer1 = createLayer();
+    auto layer2 = createLayer();
+    auto layer3 = createLayer();
+
+    EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer1, getFrameRate()).WillRepeatedly(Return(std::nullopt));
+
+    EXPECT_CALL(*layer2, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer2, getFrameRate()).WillRepeatedly(Return(std::nullopt));
+
+    EXPECT_CALL(*layer3, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer3, getFrameRate()).WillRepeatedly(Return(std::nullopt));
+
+    nsecs_t time = mTime;
+
+    EXPECT_EQ(3, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    // layer1 is active but infrequent.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer1.get(), time, time);
+        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    // layer2 is frequent and has high refresh rate.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer2.get(), time, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    // layer1 is still active but infrequent.
+    history().record(layer1.get(), time, time);
+
+    ASSERT_EQ(2, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[1].vote);
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
+    EXPECT_EQ(2, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // 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, time);
+        time += LO_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer2 still has low refresh rate.
+    // layer3 has high refresh rate but not enough history.
+    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, time);
+        }
+
+        history().record(layer3.get(), time, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(2, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[1].vote);
+    EXPECT_EQ(2, activeLayerCount());
+    EXPECT_EQ(2, frequentLayerCount(time));
+
+    // layer3 becomes recently active.
+    history().record(layer3.get(), time, time);
+    ASSERT_EQ(2, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[1].vote);
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
+    EXPECT_EQ(2, activeLayerCount());
+    EXPECT_EQ(2, frequentLayerCount(time));
+
+    // layer1 expires.
+    layer1.clear();
+    ASSERT_EQ(2, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[1].vote);
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
+    EXPECT_EQ(2, layerCount());
+    EXPECT_EQ(2, activeLayerCount());
+    EXPECT_EQ(2, frequentLayerCount(time));
+
+    // layer2 still has low refresh rate.
+    // layer3 becomes inactive.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer2.get(), time, time);
+        time += LO_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer2 expires.
+    layer2.clear();
+    EXPECT_TRUE(history().summarize(time).empty());
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    // layer3 becomes active and has high refresh rate.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer3.get(), time, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1, history().summarize(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer3 expires.
+    layer3.clear();
+    EXPECT_TRUE(history().summarize(time).empty());
+    EXPECT_EQ(0, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+} // namespace
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 86aa8fb..78009b8 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -31,16 +31,24 @@
 namespace scheduler {
 
 using RefreshRate = RefreshRateConfigs::RefreshRate;
+using LayerVoteType = RefreshRateConfigs::LayerVoteType;
+using LayerRequirement = RefreshRateConfigs::LayerRequirement;
 
 class RefreshRateConfigsTest : public testing::Test {
 protected:
     static inline const HwcConfigIndexType HWC_CONFIG_ID_60 = HwcConfigIndexType(0);
-    static inline const HwcConfigIndexType HWC_CONFIG_ID_90 = HwcConfigIndexType(1);
+    static inline const HwcConfigIndexType HWC_CONFIG_ID_72 = HwcConfigIndexType(1);
+    static inline const HwcConfigIndexType HWC_CONFIG_ID_90 = HwcConfigIndexType(2);
+    static inline const HwcConfigIndexType HWC_CONFIG_ID_120 = HwcConfigIndexType(3);
+    static inline const HwcConfigIndexType HWC_CONFIG_ID_30 = HwcConfigIndexType(4);
     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 auto VSYNC_30 = static_cast<int64_t>(1e9f / 30);
+    static constexpr auto VSYNC_60 = static_cast<int64_t>(1e9f / 60);
+    static constexpr auto VSYNC_72 = static_cast<int64_t>(1e9f / 72);
+    static constexpr auto VSYNC_90 = static_cast<int64_t>(1e9f / 90);
+    static constexpr auto VSYNC_120 = static_cast<int64_t>(1e9f / 120);
     static constexpr int64_t VSYNC_60_POINT_4 = 16666665;
-    static constexpr int64_t VSYNC_90 = 11111111;
 
     RefreshRateConfigsTest();
     ~RefreshRateConfigsTest();
@@ -212,31 +220,476 @@
     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));
+    const auto makeLayerRequirements = [](float refreshRate) -> std::vector<LayerRequirement> {
+        return {{"testLayer", LayerVoteType::Heuristic, refreshRate, 1.0f}};
+    };
+
+    EXPECT_EQ(expected90Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
+    EXPECT_EQ(expected90Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
 
     ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 60, 60, nullptr), 0);
-    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));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
 
     ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_90, 90, 90, nullptr), 0);
-    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));
+    EXPECT_EQ(expected90Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
+    EXPECT_EQ(expected90Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
+    EXPECT_EQ(expected90Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
+    EXPECT_EQ(expected90Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
+    EXPECT_EQ(expected90Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
     ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 0, 120, nullptr), 0);
-    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));
+    EXPECT_EQ(expected90Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
+    EXPECT_EQ(expected90Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContentV2_60_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,
+                                                 /*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};
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::Min;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.vote = LayerVoteType::Max;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 90.0f;
+    lr.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 45.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 30.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 24.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 60, 60, nullptr), 0);
+
+    lr.vote = LayerVoteType::Min;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.vote = LayerVoteType::Max;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 90.0f;
+    lr.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 45.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 30.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 24.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_90, 90, 90, nullptr), 0);
+
+    lr.vote = LayerVoteType::Min;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.vote = LayerVoteType::Max;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 90.0f;
+    lr.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 45.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 30.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 24.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    ASSERT_GE(refreshRateConfigs->setPolicy(HWC_CONFIG_ID_60, 0, 120, nullptr), 0);
+    lr.vote = LayerVoteType::Min;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.vote = LayerVoteType::Max;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 90.0f;
+    lr.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 45.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 30.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 24.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContentV2_60_72_90) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_72, HWC_GROUP_ID_0, VSYNC_72},
+             {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 expected72Config = {HWC_CONFIG_ID_72, VSYNC_72, HWC_GROUP_ID_0, "72fps", 70};
+    RefreshRate expected90Config = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_0, "90fps", 90};
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::Min;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.vote = LayerVoteType::Max;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 90.0f;
+    lr.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 45.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 30.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 24.0f;
+    EXPECT_EQ(expected72Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContentV2_30_60_72_90_120) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_30, HWC_GROUP_ID_0, VSYNC_30},
+             {HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_72, HWC_GROUP_ID_0, VSYNC_72},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_0, VSYNC_90},
+             {HWC_CONFIG_ID_120, HWC_GROUP_ID_0, VSYNC_120}}};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(/*refreshRateSwitching=*/true, configs,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+    ASSERT_TRUE(refreshRateConfigs->refreshRateSwitchingSupported());
+
+    RefreshRate expected30Config = {HWC_CONFIG_ID_30, VSYNC_30, HWC_GROUP_ID_0, "30fps", 30};
+    RefreshRate expected60Config = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    RefreshRate expected72Config = {HWC_CONFIG_ID_72, VSYNC_72, HWC_GROUP_ID_0, "72fps", 70};
+    RefreshRate expected90Config = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_0, "90fps", 90};
+    RefreshRate expected120Config = {HWC_CONFIG_ID_120, VSYNC_120, HWC_GROUP_ID_0, "120fps", 120};
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
+                                                LayerRequirement{.weight = 1.0f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.desiredRefreshRate = 24.0f;
+    lr1.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 60.0f;
+    lr2.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected120Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr1.desiredRefreshRate = 24.0f;
+    lr1.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 48.0f;
+    lr2.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected72Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr1.desiredRefreshRate = 24.0f;
+    lr1.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 48.0f;
+    lr2.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected72Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContentV2_30_60) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_30, HWC_GROUP_ID_0, VSYNC_30}}};
+    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 expected30Config = {HWC_CONFIG_ID_30, VSYNC_30, HWC_GROUP_ID_0, "30fps", 30};
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::Min;
+    EXPECT_EQ(expected30Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.vote = LayerVoteType::Max;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 90.0f;
+    lr.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 45.0f;
+    EXPECT_EQ(expected30Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 30.0f;
+    EXPECT_EQ(expected30Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 24.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContentV2_30_60_72_90) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_30, HWC_GROUP_ID_0, VSYNC_30},
+             {HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_72, HWC_GROUP_ID_0, VSYNC_72},
+             {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 expected30Config = {HWC_CONFIG_ID_30, VSYNC_30, HWC_GROUP_ID_0, "30fps", 30};
+    RefreshRate expected60Config = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    RefreshRate expected72Config = {HWC_CONFIG_ID_72, VSYNC_72, HWC_GROUP_ID_0, "72fps", 70};
+    RefreshRate expected90Config = {HWC_CONFIG_ID_90, VSYNC_90, HWC_GROUP_ID_0, "90fps", 90};
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::Min;
+    EXPECT_EQ(expected30Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.vote = LayerVoteType::Max;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 90.0f;
+    lr.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 45.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 30.0f;
+    EXPECT_EQ(expected30Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr.desiredRefreshRate = 24.0f;
+    EXPECT_EQ(expected72Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContentV2_PriorityTest) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_30, HWC_GROUP_ID_0, VSYNC_30},
+             {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 expected30Config = {HWC_CONFIG_ID_30, VSYNC_30, HWC_GROUP_ID_0, "30fps", 30};
+    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};
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
+                                                LayerRequirement{.weight = 1.0f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.vote = LayerVoteType::Min;
+    lr2.vote = LayerVoteType::Max;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr1.vote = LayerVoteType::Min;
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 24.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr1.vote = LayerVoteType::Min;
+    lr2.vote = LayerVoteType::Explicit;
+    lr2.desiredRefreshRate = 24.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr1.vote = LayerVoteType::Max;
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr1.vote = LayerVoteType::Max;
+    lr2.vote = LayerVoteType::Explicit;
+    lr2.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr1.vote = LayerVoteType::Heuristic;
+    lr1.desiredRefreshRate = 15.0f;
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 45.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr1.vote = LayerVoteType::Heuristic;
+    lr1.desiredRefreshRate = 30.0f;
+    lr2.vote = LayerVoteType::Explicit;
+    lr2.desiredRefreshRate = 45.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContentV2_24FpsVideo) {
+    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 expected30Config = {HWC_CONFIG_ID_30, VSYNC_30, HWC_GROUP_ID_0, "30fps", 30};
+    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};
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::Explicit;
+    for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
+        lr.desiredRefreshRate = fps;
+        const auto& refreshRate = refreshRateConfigs->getRefreshRateForContentV2(layers);
+        printf("%.2fHz chooses %s\n", fps, refreshRate.name.c_str());
+        EXPECT_EQ(expected60Config, refreshRate);
+    }
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContent_Explicit) {
+    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};
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
+                                                LayerRequirement{.weight = 1.0f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.vote = LayerVoteType::Heuristic;
+    lr1.desiredRefreshRate = 60.0f;
+    lr2.vote = LayerVoteType::Explicit;
+    lr2.desiredRefreshRate = 90.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContent(layers));
+
+    lr1.vote = LayerVoteType::Heuristic;
+    lr1.desiredRefreshRate = 90.0f;
+    lr2.vote = LayerVoteType::Explicit;
+    lr2.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContent(layers));
+}
+
+TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContentV2_Explicit) {
+    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};
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
+                                                LayerRequirement{.weight = 1.0f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.vote = LayerVoteType::Heuristic;
+    lr1.desiredRefreshRate = 60.0f;
+    lr2.vote = LayerVoteType::Explicit;
+    lr2.desiredRefreshRate = 90.0f;
+    EXPECT_EQ(expected90Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
+
+    lr1.vote = LayerVoteType::Heuristic;
+    lr1.desiredRefreshRate = 90.0f;
+    lr2.vote = LayerVoteType::Explicit;
+    lr2.desiredRefreshRate = 60.0f;
+    EXPECT_EQ(expected60Config, refreshRateConfigs->getRefreshRateForContentV2(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, testInPolicy) {
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index b1ecf4d..82a00ee 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -76,7 +76,7 @@
             scheduler::RefreshRateConfigs>(/*refreshRateSwitching=*/false, configs,
                                            /*currentConfig=*/HwcConfigIndexType(0));
 
-    mScheduler = std::make_unique<TestableScheduler>(*mRefreshRateConfigs);
+    mScheduler = std::make_unique<TestableScheduler>(*mRefreshRateConfigs, false);
 
     auto eventThread = std::make_unique<mock::EventThread>();
     mEventThread = eventThread.get();
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index a67c24c..52da34b 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -28,16 +28,25 @@
 
 class TestableScheduler : public Scheduler, private ISchedulerCallback {
 public:
-    explicit TestableScheduler(const scheduler::RefreshRateConfigs& configs)
-          : Scheduler([](bool) {}, configs, *this) {
-        mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
+    TestableScheduler(const scheduler::RefreshRateConfigs& configs, bool useContentDetectionV2)
+          : Scheduler([](bool) {}, configs, *this, useContentDetectionV2) {
+        if (mUseContentDetectionV2) {
+            mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>();
+        } else {
+            mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
+        }
     }
 
     TestableScheduler(std::unique_ptr<DispSync> primaryDispSync,
                       std::unique_ptr<EventControlThread> eventControlThread,
-                      const scheduler::RefreshRateConfigs& configs)
-          : Scheduler(std::move(primaryDispSync), std::move(eventControlThread), configs, *this) {
-        mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
+                      const scheduler::RefreshRateConfigs& configs, bool useContentDetectionV2)
+          : Scheduler(std::move(primaryDispSync), std::move(eventControlThread), configs, *this,
+                      useContentDetectionV2) {
+        if (mUseContentDetectionV2) {
+            mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>();
+        } else {
+            mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
+        }
     }
 
     // Used to inject mock event thread.
@@ -46,7 +55,13 @@
     }
 
     size_t layerHistorySize() const NO_THREAD_SAFETY_ANALYSIS {
-        return static_cast<scheduler::impl::LayerHistory*>(mLayerHistory.get())->mLayerInfos.size();
+        if (mUseContentDetectionV2) {
+            return static_cast<scheduler::impl::LayerHistoryV2*>(mLayerHistory.get())
+                    ->mLayerInfos.size();
+        } else {
+            return static_cast<scheduler::impl::LayerHistory*>(mLayerHistory.get())
+                    ->mLayerInfos.size();
+        }
     }
 
     /* ------------------------------------------------------------------------
@@ -60,6 +75,9 @@
     auto mutableLayerHistory() {
         return static_cast<scheduler::impl::LayerHistory*>(mLayerHistory.get());
     }
+    auto mutableLayerHistoryV2() {
+        return static_cast<scheduler::impl::LayerHistoryV2*>(mLayerHistory.get());
+    }
 
     ~TestableScheduler() {
         // All these pointer and container clears help ensure that GMock does
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 8ddb872..2491533 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -198,7 +198,8 @@
     void setupScheduler(std::unique_ptr<DispSync> primaryDispSync,
                         std::unique_ptr<EventControlThread> eventControlThread,
                         std::unique_ptr<EventThread> appEventThread,
-                        std::unique_ptr<EventThread> sfEventThread) {
+                        std::unique_ptr<EventThread> sfEventThread,
+                        bool useContentDetectionV2 = false) {
         std::vector<scheduler::RefreshRateConfigs::InputConfig> configs{
                 {{HwcConfigIndexType(0), HwcConfigGroupType(0), 16666667}}};
         mFlinger->mRefreshRateConfigs = std::make_unique<
@@ -213,7 +214,7 @@
 
         mScheduler =
                 new TestableScheduler(std::move(primaryDispSync), std::move(eventControlThread),
-                                      *mFlinger->mRefreshRateConfigs);
+                                      *mFlinger->mRefreshRateConfigs, useContentDetectionV2);
 
         mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
         mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
@@ -443,7 +444,7 @@
         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 hwc2_config_t DEFAULT_ACTIVE_CONFIG = 0;
         static constexpr int32_t DEFAULT_POWER_MODE = 2;
 
         FakeHwcDisplayInjector(DisplayId displayId, HWC2::DisplayType hwcDisplayType,
@@ -465,7 +466,7 @@
             return *this;
         }
 
-        auto& setRefreshRate(uint32_t refreshRate) {
+        auto& setRefreshRate(int32_t refreshRate) {
             mRefreshRate = refreshRate;
             return *this;
         }
@@ -480,7 +481,7 @@
             return *this;
         }
 
-        auto& setActiveConfig(int32_t config) {
+        auto& setActiveConfig(hwc2_config_t config) {
             mActiveConfig = config;
             return *this;
         }
@@ -513,7 +514,7 @@
             config.setDpiX(mDpiX);
             config.setDpiY(mDpiY);
             config.setConfigGroup(mConfigGroup);
-            display->mutableConfigs().emplace(mActiveConfig, config.build());
+            display->mutableConfigs().emplace(static_cast<int32_t>(mActiveConfig), config.build());
             display->mutableIsConnected() = true;
             display->setPowerMode(static_cast<HWC2::PowerMode>(mPowerMode));
 
@@ -534,11 +535,11 @@
         hwc2_display_t mHwcDisplayId = DEFAULT_HWC_DISPLAY_ID;
         int32_t mWidth = DEFAULT_WIDTH;
         int32_t mHeight = DEFAULT_HEIGHT;
-        uint32_t mRefreshRate = DEFAULT_REFRESH_RATE;
+        int32_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;
+        hwc2_config_t mActiveConfig = DEFAULT_ACTIVE_CONFIG;
         int32_t mPowerMode = DEFAULT_POWER_MODE;
         const std::unordered_set<HWC2::Capability>* mCapabilities = nullptr;
     };
diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
index 1fd0e61..494e73d 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockLayer.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
@@ -31,6 +31,7 @@
     MOCK_METHOD0(getFrameSelectionPriority, int32_t());
     MOCK_CONST_METHOD0(isVisible, bool());
     MOCK_METHOD0(createClone, sp<Layer>());
+    MOCK_CONST_METHOD0(getFrameRate, std::optional<float>());
 };
 
 } // namespace android::mock