SurfaceFlinger: stabilize heuristic refresh rate calculation

When calculating layer's refresh rate heuristically, also store
a history of the last refresh rate calculated. We use this list to
determine how consistent is the refresh rate. If the refresh rate is
not consistent, then we keep reporting the last consistent refresh rate
and wait for the refresh rate to stabilize again.

Test: Play at 60fps video in XPlayer
Bug: 157540021

Change-Id: If3b525820d298cc5835dddf73f327501c8a18964
diff --git a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
index 6216f6e..ee612b0 100644
--- a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
@@ -58,30 +58,21 @@
     return atoi(value);
 }
 
-void trace(const wp<Layer>& weak, LayerHistory::LayerVoteType type, int fps) {
+void trace(const wp<Layer>& weak, const LayerInfoV2& info, LayerHistory::LayerVoteType type,
+           int fps) {
     const auto layer = weak.promote();
     if (!layer) return;
 
-    const auto makeTag = [layer](LayerHistory::LayerVoteType vote) {
-        return "LFPS " + RefreshRateConfigs::layerVoteTypeString(vote) + " " + layer->getName();
+    const auto traceType = [&](LayerHistory::LayerVoteType checkedType, int value) {
+        ATRACE_INT(info.getTraceTag(checkedType), type == checkedType ? value : 0);
     };
 
-    const auto noVoteTag = makeTag(LayerHistory::LayerVoteType::NoVote);
-    const auto heuristicVoteTag = makeTag(LayerHistory::LayerVoteType::Heuristic);
-    const auto explicitDefaultVoteTag = makeTag(LayerHistory::LayerVoteType::ExplicitDefault);
-    const auto explicitExactOrMultipleVoteTag =
-            makeTag(LayerHistory::LayerVoteType::ExplicitExactOrMultiple);
-    const auto minVoteTag = makeTag(LayerHistory::LayerVoteType::Min);
-    const auto maxVoteTag = makeTag(LayerHistory::LayerVoteType::Max);
-
-    ATRACE_INT(noVoteTag.c_str(), type == LayerHistory::LayerVoteType::NoVote ? 1 : 0);
-    ATRACE_INT(heuristicVoteTag.c_str(), type == LayerHistory::LayerVoteType::Heuristic ? fps : 0);
-    ATRACE_INT(explicitDefaultVoteTag.c_str(),
-               type == LayerHistory::LayerVoteType::ExplicitDefault ? fps : 0);
-    ATRACE_INT(explicitExactOrMultipleVoteTag.c_str(),
-               type == LayerHistory::LayerVoteType::ExplicitExactOrMultiple ? fps : 0);
-    ATRACE_INT(minVoteTag.c_str(), type == LayerHistory::LayerVoteType::Min ? 1 : 0);
-    ATRACE_INT(maxVoteTag.c_str(), type == LayerHistory::LayerVoteType::Max ? 1 : 0);
+    traceType(LayerHistory::LayerVoteType::NoVote, 1);
+    traceType(LayerHistory::LayerVoteType::Heuristic, fps);
+    traceType(LayerHistory::LayerVoteType::ExplicitDefault, fps);
+    traceType(LayerHistory::LayerVoteType::ExplicitExactOrMultiple, fps);
+    traceType(LayerHistory::LayerVoteType::Min, 1);
+    traceType(LayerHistory::LayerVoteType::Max, 1);
 
     ALOGD("%s: %s @ %d Hz", __FUNCTION__, layer->getName().c_str(), fps);
 }
@@ -89,6 +80,7 @@
 
 LayerHistoryV2::LayerHistoryV2(const scheduler::RefreshRateConfigs& refreshRateConfigs)
       : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {
+    LayerInfoV2::setTraceEnabled(mTraceEnabled);
     LayerInfoV2::setRefreshRateConfigs(refreshRateConfigs);
 }
 
@@ -154,7 +146,7 @@
         summary.push_back({strong->getName(), type, refreshRate, weight});
 
         if (CC_UNLIKELY(mTraceEnabled)) {
-            trace(layer, type, static_cast<int>(std::round(refreshRate)));
+            trace(layer, *info, type, static_cast<int>(std::round(refreshRate)));
         }
     }
 
@@ -193,7 +185,7 @@
         }
 
         if (CC_UNLIKELY(mTraceEnabled)) {
-            trace(weak, LayerHistory::LayerVoteType::NoVote, 0);
+            trace(weak, *info, LayerHistory::LayerVoteType::NoVote, 0);
         }
 
         info->onLayerInactive(now);
@@ -220,4 +212,5 @@
         info->clearHistory(systemTime());
     }
 }
+
 } // namespace android::scheduler::impl
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index cb81ca2..820624b 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -98,9 +98,9 @@
                 return false;
             }
 
-            // The layer had to publish at least HISTORY_SIZE or HISTORY_TIME of updates
+            // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
             if (mElements.size() < HISTORY_SIZE &&
-                mElements.back() - mElements.front() < HISTORY_TIME.count()) {
+                mElements.back() - mElements.front() < HISTORY_DURATION.count()) {
                 return false;
             }
 
@@ -124,7 +124,7 @@
 
     private:
         std::deque<nsecs_t> mElements;
-        static constexpr std::chrono::nanoseconds HISTORY_TIME = 1s;
+        static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;
     };
 
     friend class LayerHistoryTest;
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
index f4bbc37..44f20d0 100644
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
@@ -15,26 +15,31 @@
  */
 
 // #define LOG_NDEBUG 0
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include "LayerInfoV2.h"
 
 #include <algorithm>
 #include <utility>
 
+#include <cutils/compiler.h>
+#include <cutils/trace.h>
+
 #undef LOG_TAG
 #define LOG_TAG "LayerInfoV2"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 namespace android::scheduler {
 
 const RefreshRateConfigs* LayerInfoV2::sRefreshRateConfigs = nullptr;
+bool LayerInfoV2::sTraceEnabled = false;
 
 LayerInfoV2::LayerInfoV2(const std::string& name, nsecs_t highRefreshRatePeriod,
                          LayerHistory::LayerVoteType defaultVote)
       : mName(name),
         mHighRefreshRatePeriod(highRefreshRatePeriod),
         mDefaultVote(defaultVote),
-        mLayerVote({defaultVote, 0.0f}) {}
+        mLayerVote({defaultVote, 0.0f}),
+        mRefreshRateHistory(name) {}
 
 void LayerInfoV2::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now,
                                      LayerUpdateType updateType, bool pendingConfigChange) {
@@ -94,24 +99,28 @@
 }
 
 bool LayerInfoV2::hasEnoughDataForHeuristic() const {
-    // The layer had to publish at least HISTORY_SIZE or HISTORY_TIME of updates
+    // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
     if (mFrameTimes.size() < 2) {
+        ALOGV("fewer than 2 frames recorded: %zu", mFrameTimes.size());
         return false;
     }
 
     if (!isFrameTimeValid(mFrameTimes.front())) {
+        ALOGV("stale frames still captured");
         return false;
     }
 
-    if (mFrameTimes.size() < HISTORY_SIZE &&
-        mFrameTimes.back().queueTime - mFrameTimes.front().queueTime < HISTORY_TIME.count()) {
+    const auto totalDuration = mFrameTimes.back().queueTime - mFrameTimes.front().queueTime;
+    if (mFrameTimes.size() < HISTORY_SIZE && totalDuration < HISTORY_DURATION.count()) {
+        ALOGV("not enough frames captured: %zu | %.2f seconds", mFrameTimes.size(),
+              totalDuration / 1e9f);
         return false;
     }
 
     return true;
 }
 
-std::pair<nsecs_t, bool> LayerInfoV2::calculateAverageFrameTime() const {
+std::optional<nsecs_t> LayerInfoV2::calculateAverageFrameTime() const {
     nsecs_t totalPresentTimeDeltas = 0;
     nsecs_t totalQueueTimeDeltas = 0;
     bool missingPresentTime = false;
@@ -119,15 +128,20 @@
     for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
         // Ignore frames captured during a config change
         if (it->pendingConfigChange || (it + 1)->pendingConfigChange) {
-            continue;
+            return std::nullopt;
         }
 
         totalQueueTimeDeltas +=
                 std::max(((it + 1)->queueTime - it->queueTime), mHighRefreshRatePeriod);
         numFrames++;
 
-        if (it->presetTime == 0 || (it + 1)->presetTime == 0) {
+        if (!missingPresentTime && (it->presetTime == 0 || (it + 1)->presetTime == 0)) {
             missingPresentTime = true;
+            // If there are no presentation timestamps and we haven't calculated
+            // one in the past then we can't calculate the refresh rate
+            if (mLastRefreshRate.reported == 0) {
+                return std::nullopt;
+            }
             continue;
         }
 
@@ -142,61 +156,46 @@
     // when implementing render ahead for specific refresh rates. When hwui no longer provides
     // presentation timestamps we look at the queue time to see if the current refresh rate still
     // matches the content.
+
     const auto averageFrameTime =
             static_cast<float>(missingPresentTime ? totalQueueTimeDeltas : totalPresentTimeDeltas) /
             numFrames;
-    return {static_cast<nsecs_t>(averageFrameTime), missingPresentTime};
+    return static_cast<nsecs_t>(averageFrameTime);
 }
 
-bool LayerInfoV2::isRefreshRateStable(nsecs_t averageFrameTime, bool missingPresentTime) const {
-    for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
-        // Ignore frames captured during a config change
-        if (it->pendingConfigChange || (it + 1)->pendingConfigChange) {
-            continue;
-        }
-        const auto presentTimeDeltas = [&] {
-            const auto delta = missingPresentTime ? (it + 1)->queueTime - it->queueTime
-                                                  : (it + 1)->presetTime - it->presetTime;
-            return std::max(delta, mHighRefreshRatePeriod);
-        }();
-
-        if (std::abs(presentTimeDeltas - averageFrameTime) > 2 * averageFrameTime) {
-            return false;
-        }
-    }
-
-    return true;
-}
-
-std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible() {
+std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible(nsecs_t now) {
     static constexpr float MARGIN = 1.0f; // 1Hz
     if (!hasEnoughDataForHeuristic()) {
         ALOGV("Not enough data");
         return std::nullopt;
     }
 
-    const auto [averageFrameTime, missingPresentTime] = calculateAverageFrameTime();
+    const auto averageFrameTime = calculateAverageFrameTime();
+    if (averageFrameTime.has_value()) {
+        const auto refreshRate = 1e9f / *averageFrameTime;
+        const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);
+        if (refreshRateConsistent) {
+            const auto knownRefreshRate =
+                    sRefreshRateConfigs->findClosestKnownFrameRate(refreshRate);
 
-    // If there are no presentation timestamps provided we can't calculate the refresh rate
-    if (missingPresentTime && mLastRefreshRate.reported == 0) {
-        return std::nullopt;
+            // To avoid oscillation, use the last calculated refresh rate if it is
+            // close enough
+            if (std::abs(mLastRefreshRate.calculated - refreshRate) > MARGIN &&
+                mLastRefreshRate.reported != knownRefreshRate) {
+                mLastRefreshRate.calculated = refreshRate;
+                mLastRefreshRate.reported = knownRefreshRate;
+            }
+
+            ALOGV("%s %.2fHz rounded to nearest known frame rate %.2fHz", mName.c_str(),
+                  refreshRate, mLastRefreshRate.reported);
+        } else {
+            ALOGV("%s Not stable (%.2fHz) returning last known frame rate %.2fHz", mName.c_str(),
+                  refreshRate, mLastRefreshRate.reported);
+        }
     }
 
-    if (!isRefreshRateStable(averageFrameTime, missingPresentTime)) {
-        return std::nullopt;
-    }
-
-    const auto refreshRate = 1e9f / averageFrameTime;
-    const auto knownRefreshRate = sRefreshRateConfigs->findClosestKnownFrameRate(refreshRate);
-    if (std::abs(mLastRefreshRate.calculated - refreshRate) > MARGIN &&
-        mLastRefreshRate.reported != knownRefreshRate) {
-        mLastRefreshRate.calculated = refreshRate;
-        mLastRefreshRate.reported = knownRefreshRate;
-    }
-
-    ALOGV("%s %.2fHz rounded to nearest known frame rate %.2fHz", mName.c_str(), refreshRate,
-          mLastRefreshRate.reported);
-    return mLastRefreshRate.reported;
+    return mLastRefreshRate.reported == 0 ? std::nullopt
+                                          : std::make_optional(mLastRefreshRate.reported);
 }
 
 std::pair<LayerHistory::LayerVoteType, float> LayerInfoV2::getRefreshRate(nsecs_t now) {
@@ -207,15 +206,24 @@
 
     if (isAnimating(now)) {
         ALOGV("%s is animating", mName.c_str());
+        mLastRefreshRate.animatingOrInfrequent = true;
         return {LayerHistory::LayerVoteType::Max, 0};
     }
 
     if (!isFrequent(now)) {
         ALOGV("%s is infrequent", mName.c_str());
+        mLastRefreshRate.animatingOrInfrequent = true;
         return {LayerHistory::LayerVoteType::Min, 0};
     }
 
-    auto refreshRate = calculateRefreshRateIfPossible();
+    // If the layer was previously tagged as animating or infrequent, we clear
+    // the history as it is likely the layer just changed its behavior
+    // and we should not look at stale data
+    if (mLastRefreshRate.animatingOrInfrequent) {
+        clearHistory(now);
+    }
+
+    auto refreshRate = calculateRefreshRateIfPossible(now);
     if (refreshRate.has_value()) {
         ALOGV("%s calculated refresh rate: %.2f", mName.c_str(), refreshRate.value());
         return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
@@ -225,4 +233,65 @@
     return {LayerHistory::LayerVoteType::Max, 0};
 }
 
+const char* LayerInfoV2::getTraceTag(android::scheduler::LayerHistory::LayerVoteType type) const {
+    if (mTraceTags.count(type) == 0) {
+        const auto tag = "LFPS " + mName + " " + RefreshRateConfigs::layerVoteTypeString(type);
+        mTraceTags.emplace(type, tag);
+    }
+
+    return mTraceTags.at(type).c_str();
+}
+
+LayerInfoV2::RefreshRateHistory::HeuristicTraceTagData
+LayerInfoV2::RefreshRateHistory::makeHeuristicTraceTagData() const {
+    const std::string prefix = "LFPS ";
+    const std::string suffix = "Heuristic ";
+    return {.min = prefix + mName + suffix + "min",
+            .max = prefix + mName + suffix + "max",
+            .consistent = prefix + mName + suffix + "consistent",
+            .average = prefix + mName + suffix + "average"};
+}
+
+void LayerInfoV2::RefreshRateHistory::clear() {
+    mRefreshRates.clear();
+}
+
+bool LayerInfoV2::RefreshRateHistory::add(float refreshRate, nsecs_t now) {
+    mRefreshRates.push_back({refreshRate, now});
+    while (mRefreshRates.size() >= HISTORY_SIZE ||
+           now - mRefreshRates.front().timestamp > HISTORY_DURATION.count()) {
+        mRefreshRates.pop_front();
+    }
+
+    if (CC_UNLIKELY(sTraceEnabled)) {
+        if (!mHeuristicTraceTagData.has_value()) {
+            mHeuristicTraceTagData = makeHeuristicTraceTagData();
+        }
+
+        ATRACE_INT(mHeuristicTraceTagData->average.c_str(), static_cast<int>(refreshRate));
+    }
+
+    return isConsistent();
+}
+
+bool LayerInfoV2::RefreshRateHistory::isConsistent() const {
+    if (mRefreshRates.empty()) return true;
+
+    const auto max = std::max_element(mRefreshRates.begin(), mRefreshRates.end());
+    const auto min = std::min_element(mRefreshRates.begin(), mRefreshRates.end());
+    const auto consistent = max->refreshRate - min->refreshRate <= MARGIN_FPS;
+
+    if (CC_UNLIKELY(sTraceEnabled)) {
+        if (!mHeuristicTraceTagData.has_value()) {
+            mHeuristicTraceTagData = makeHeuristicTraceTagData();
+        }
+
+        ATRACE_INT(mHeuristicTraceTagData->max.c_str(), static_cast<int>(max->refreshRate));
+        ATRACE_INT(mHeuristicTraceTagData->min.c_str(), static_cast<int>(min->refreshRate));
+        ATRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
+    }
+
+    return consistent;
+}
+
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.h b/services/surfaceflinger/Scheduler/LayerInfoV2.h
index c434963..33dc66f 100644
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.h
+++ b/services/surfaceflinger/Scheduler/LayerInfoV2.h
@@ -56,6 +56,8 @@
     friend class LayerHistoryTestV2;
 
 public:
+    static void setTraceEnabled(bool enabled) { sTraceEnabled = enabled; }
+
     static void setRefreshRateConfigs(const RefreshRateConfigs& refreshRateConfigs) {
         sRefreshRateConfigs = &refreshRateConfigs;
     }
@@ -90,6 +92,9 @@
     // updated time, the updated time is the present time.
     nsecs_t getLastUpdatedTime() const { return mLastUpdatedTime; }
 
+    // Returns a C string for tracing a vote
+    const char* getTraceTag(LayerHistory::LayerVoteType type) const;
+
     void onLayerInactive(nsecs_t now) {
         // Mark mFrameTimeValidSince to now to ignore all previous frame times.
         // We are not deleting the old frame to keep track of whether we should treat the first
@@ -98,6 +103,7 @@
         const auto timePoint = std::chrono::nanoseconds(now);
         mFrameTimeValidSince = std::chrono::time_point<std::chrono::steady_clock>(timePoint);
         mLastRefreshRate = {};
+        mRefreshRateHistory.clear();
     }
 
     void clearHistory(nsecs_t now) {
@@ -113,12 +119,73 @@
         bool pendingConfigChange;
     };
 
+    // Holds information about the calculated and reported refresh rate
+    struct RefreshRateHeuristicData {
+        // Rate calculated on the layer
+        float calculated = 0.0f;
+        // Last reported rate for LayerInfoV2::getRefreshRate()
+        float reported = 0.0f;
+        // Whether the last reported rate for LayerInfoV2::getRefreshRate()
+        // was due to animation or infrequent updates
+        bool animatingOrInfrequent = false;
+    };
+
+    // Holds information about the layer vote
+    struct LayerVote {
+        LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
+        float fps = 0.0f;
+    };
+
+    // Class to store past calculated refresh rate and determine whether
+    // the refresh rate calculated is consistent with past values
+    class RefreshRateHistory {
+    public:
+        static constexpr auto HISTORY_SIZE = 90;
+        static constexpr std::chrono::nanoseconds HISTORY_DURATION = 2s;
+
+        RefreshRateHistory(const std::string& name) : mName(name) {}
+
+        // Clears History
+        void clear();
+
+        // Adds a new refresh rate and returns true if it is consistent
+        bool add(float refreshRate, nsecs_t now);
+
+    private:
+        friend class LayerHistoryTestV2;
+
+        // Holds the refresh rate when it was calculated
+        struct RefreshRateData {
+            float refreshRate = 0.0f;
+            nsecs_t timestamp = 0;
+
+            bool operator<(const RefreshRateData& other) const {
+                return refreshRate < other.refreshRate;
+            }
+        };
+
+        // Holds tracing strings
+        struct HeuristicTraceTagData {
+            std::string min;
+            std::string max;
+            std::string consistent;
+            std::string average;
+        };
+
+        bool isConsistent() const;
+        HeuristicTraceTagData makeHeuristicTraceTagData() const;
+
+        const std::string mName;
+        mutable std::optional<HeuristicTraceTagData> mHeuristicTraceTagData;
+        std::deque<RefreshRateData> mRefreshRates;
+        static constexpr float MARGIN_FPS = 1.0;
+    };
+
     bool isFrequent(nsecs_t now) const;
     bool isAnimating(nsecs_t now) const;
     bool hasEnoughDataForHeuristic() const;
-    std::optional<float> calculateRefreshRateIfPossible();
-    std::pair<nsecs_t, bool> calculateAverageFrameTime() const;
-    bool isRefreshRateStable(nsecs_t averageFrameTime, bool missingPresentTime) const;
+    std::optional<float> calculateRefreshRateIfPossible(nsecs_t now);
+    std::optional<nsecs_t> calculateAverageFrameTime() const;
     bool isFrameTimeValid(const FrameTimeData&) const;
 
     const std::string mName;
@@ -127,32 +194,27 @@
     const nsecs_t mHighRefreshRatePeriod;
     LayerHistory::LayerVoteType mDefaultVote;
 
+    LayerVote mLayerVote;
+
     nsecs_t mLastUpdatedTime = 0;
 
     nsecs_t mLastAnimationTime = 0;
 
-    // Holds information about the calculated and reported refresh rate
-    struct RefreshRateHeuristicData {
-        float calculated = 0.0f; // Rate calculated on the layer
-        float reported = 0.0f;   // Last reported rate for LayerInfoV2::getRefreshRate()
-    };
-
     RefreshRateHeuristicData mLastRefreshRate;
 
-    // Holds information about the layer vote
-    struct {
-        LayerHistory::LayerVoteType type;
-        float fps;
-    } mLayerVote;
-
     std::deque<FrameTimeData> mFrameTimes;
     std::chrono::time_point<std::chrono::steady_clock> mFrameTimeValidSince =
             std::chrono::steady_clock::now();
-    static constexpr size_t HISTORY_SIZE = 90;
-    static constexpr std::chrono::nanoseconds HISTORY_TIME = 1s;
+    static constexpr size_t HISTORY_SIZE = RefreshRateHistory::HISTORY_SIZE;
+    static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;
+
+    RefreshRateHistory mRefreshRateHistory;
+
+    mutable std::unordered_map<LayerHistory::LayerVoteType, std::string> mTraceTags;
 
     // Shared for all LayerInfo instances
     static const RefreshRateConfigs* sRefreshRateConfigs;
+    static bool sTraceEnabled;
 };
 
 } // namespace scheduler
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
index 2d3b01f..afd2b71 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
@@ -38,7 +38,9 @@
     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 auto FREQUENT_LAYER_WINDOW_SIZE = LayerInfoV2::FREQUENT_LAYER_WINDOW_SIZE;
-    static constexpr auto PRESENT_TIME_HISTORY_TIME = LayerInfoV2::HISTORY_TIME;
+    static constexpr auto PRESENT_TIME_HISTORY_DURATION = LayerInfoV2::HISTORY_DURATION;
+    static constexpr auto REFRESH_RATE_AVERAGE_HISTORY_DURATION =
+            LayerInfoV2::RefreshRateHistory::HISTORY_DURATION;
 
     static constexpr float LO_FPS = 30.f;
     static constexpr auto LO_FPS_PERIOD = static_cast<nsecs_t>(1e9f / LO_FPS);
@@ -84,19 +86,20 @@
         return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger(), std::move(name)));
     }
 
-    void recordFramesAndExpect(const sp<mock::MockLayer>& layer, float frameRate,
-                               float desiredRefreshRate) {
-        nsecs_t time = systemTime();
+    void recordFramesAndExpect(const sp<mock::MockLayer>& layer, nsecs_t& time, float frameRate,
+                               float desiredRefreshRate, int numFrames) {
         const nsecs_t framePeriod = static_cast<nsecs_t>(1e9f / frameRate);
-
-        for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        impl::LayerHistoryV2::Summary summary;
+        for (int i = 0; i < numFrames; i++) {
             history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
             time += framePeriod;
+
+            summary = history().summarize(time);
         }
 
-        ASSERT_EQ(1, history().summarize(time).size());
-        EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
-        EXPECT_FLOAT_EQ(desiredRefreshRate, history().summarize(time)[0].desiredRefreshRate)
+        ASSERT_EQ(1, summary.size());
+        ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+        ASSERT_FLOAT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate)
                 << "Frame rate is " << frameRate;
     }
 
@@ -362,14 +365,17 @@
     EXPECT_EQ(0, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
 
+    impl::LayerHistoryV2::Summary summary;
+
     // layer1 is active but infrequent.
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
         history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+        summary = history().summarize(time);
     }
 
-    ASSERT_EQ(1, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+    ASSERT_EQ(1, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
 
@@ -377,28 +383,30 @@
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
         history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += HI_FPS_PERIOD;
+        summary = history().summarize(time);
     }
 
     // layer1 is still active but infrequent.
     history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
 
-    ASSERT_EQ(2, history().summarize(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
-    ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[1].vote);
+    ASSERT_EQ(2, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
+    ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[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++) {
+    for (int i = 0; i < 2 * PRESENT_TIME_HISTORY_SIZE; i++) {
         history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += LO_FPS_PERIOD;
+        summary = history().summarize(time);
     }
 
-    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);
+    ASSERT_EQ(1, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -412,33 +420,36 @@
 
         history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += HI_FPS_PERIOD;
+        summary = history().summarize(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::Max, history().summarize(time)[1].vote);
+    ASSERT_EQ(2, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[1].vote);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
 
     // layer3 becomes recently active.
     history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-    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);
+    summary = history().summarize(time);
+    ASSERT_EQ(2, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
+    EXPECT_FLOAT_EQ(HI_FPS, summary[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);
+    summary = history().summarize(time);
+    ASSERT_EQ(2, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
+    EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
     EXPECT_EQ(2, layerCount());
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
@@ -448,37 +459,41 @@
     for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
         history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += LO_FPS_PERIOD;
+        summary = history().summarize(time);
     }
 
-    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);
+    ASSERT_EQ(1, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
     // layer2 expires.
     layer2.clear();
-    EXPECT_TRUE(history().summarize(time).empty());
+    summary = history().summarize(time);
+    EXPECT_TRUE(summary.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++) {
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
         history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += HI_FPS_PERIOD;
+        summary = history().summarize(time);
     }
 
-    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);
+    ASSERT_EQ(1, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_FLOAT_EQ(HI_FPS, summary[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());
+    summary = history().summarize(time);
+    EXPECT_TRUE(summary.empty());
     EXPECT_EQ(0, layerCount());
     EXPECT_EQ(0, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
@@ -621,21 +636,39 @@
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
 
+    nsecs_t time = systemTime();
     for (float fps = 54.0f; fps < 65.0f; fps += 0.1f) {
-        recordFramesAndExpect(layer, fps, 60.0f);
+        recordFramesAndExpect(layer, time, fps, 60.0f, PRESENT_TIME_HISTORY_SIZE);
     }
 }
 
+TEST_F(LayerHistoryTestV2, heuristicLayer60_30Hz) {
+    const auto layer = createLayer();
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    nsecs_t time = systemTime();
+    recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+
+    recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 30.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 30.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 60.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+}
+
 TEST_F(LayerHistoryTestV2, heuristicLayerNotOscillating) {
     const auto layer = createLayer();
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
 
-    recordFramesAndExpect(layer, 27.10f, 30.0f);
-    recordFramesAndExpect(layer, 26.50f, 30.0f);
-    recordFramesAndExpect(layer, 25.90f, 24.0f);
-    recordFramesAndExpect(layer, 26.50f, 24.0f);
-    recordFramesAndExpect(layer, 27.10f, 30.0f);
+    nsecs_t time = systemTime();
+
+    recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26.90f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26.00f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26.90f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
 }
 
 class LayerHistoryTestV2Parameterized
@@ -680,7 +713,7 @@
             infrequentLayerUpdates++;
         }
 
-        if (time - startTime > PRESENT_TIME_HISTORY_TIME.count()) {
+        if (time - startTime > PRESENT_TIME_HISTORY_DURATION.count()) {
             ASSERT_NE(0, history().summarize(time).size());
             ASSERT_GE(2, history().summarize(time).size());