SF: Optimize layer history in scheduler

Register layers with LayerHistory by storing a weak pointer rather than
allocating a LayerHandle. Query layer visibility when needed instead of
synchronizing it to LayerInfo whenever Layer::isVisible is called. Store
active/inactive layers in contiguous memory instead of two hash maps for
cache efficiency, and minimal allocation and run time of operations like
clearing history. Remove redundant ref-counting, locking, and frequency-
period conversion in LayerInfo. Avoid sleeping in unit tests.

This is also prework for per-display layer history.

Bug: 130554049
Bug: 134772048
Test: go/wm-smoke with debug.sf.layer_history_trace
Test: C2/F2 test cases from spreadsheet
Test: LayerHistoryTest with new test cases
Change-Id: Ibfcfe46cd76ebd93b916d4a0c737a19e837d4ff1
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index 603a9f7..a25709c 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -107,11 +107,8 @@
 }
 
 bool BufferLayer::isVisible() const {
-    bool visible = !(isHiddenByPolicy()) && getAlpha() > 0.0f &&
+    return !isHiddenByPolicy() && getAlpha() > 0.0f &&
             (mBufferInfo.mBuffer != nullptr || mSidebandStream != nullptr);
-    mFlinger->mScheduler->setLayerVisibility(mSchedulerLayerHandle, visible);
-
-    return visible;
 }
 
 bool BufferLayer::isFixedSize() const {
diff --git a/services/surfaceflinger/BufferQueueLayer.cpp b/services/surfaceflinger/BufferQueueLayer.cpp
index 8cc1831..c24cdaa 100644
--- a/services/surfaceflinger/BufferQueueLayer.cpp
+++ b/services/surfaceflinger/BufferQueueLayer.cpp
@@ -389,8 +389,8 @@
     { // Autolock scope
         if (mFlinger->mUseSmart90ForVideo) {
             const nsecs_t presentTime = item.mIsAutoTimestamp ? 0 : item.mTimestamp;
-            mFlinger->mScheduler->addLayerPresentTimeAndHDR(mSchedulerLayerHandle, presentTime,
-                                                            item.mHdrMetadata.validTypes != 0);
+            mFlinger->mScheduler->recordLayerHistory(this, presentTime,
+                                                     item.mHdrMetadata.validTypes != 0);
         }
 
         Mutex::Autolock lock(mQueueItemLock);
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index 85d2253..5768edd 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -249,8 +249,8 @@
 
     if (mFlinger->mUseSmart90ForVideo) {
         const nsecs_t presentTime = (desiredPresentTime == -1) ? 0 : desiredPresentTime;
-        mFlinger->mScheduler->addLayerPresentTimeAndHDR(mSchedulerLayerHandle, presentTime,
-                                                        mCurrentState.hdrMetadata.validTypes != 0);
+        mFlinger->mScheduler->recordLayerHistory(this, presentTime,
+                                                 mCurrentState.hdrMetadata.validTypes != 0);
     }
 
     return true;
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 37caab2..e19b79b 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -118,10 +118,9 @@
     mFrameEventHistory.initializeCompositorTiming(compositorTiming);
     mFrameTracker.setDisplayRefreshPeriod(compositorTiming.interval);
 
-    mSchedulerLayerHandle = mFlinger->mScheduler->registerLayer(mName.c_str(), mWindowType);
     mCallingPid = args.callingPid;
     mCallingUid = args.callingUid;
-    mFlinger->onLayerCreated();
+    mFlinger->onLayerCreated(this);
 }
 
 Layer::~Layer() {
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index ad81920..fdac98f 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -221,6 +221,8 @@
     explicit Layer(const LayerCreationArgs& args);
     virtual ~Layer();
 
+    int getWindowType() const { return mWindowType; }
+
     void setPrimaryDisplayOnly() { mPrimaryDisplayOnly = true; }
     bool getPrimaryDisplayOnly() const { return mPrimaryDisplayOnly; }
 
@@ -886,9 +888,6 @@
     // Window types from WindowManager.LayoutParams
     const int mWindowType;
 
-    // This is populated if the layer is registered with Scheduler for tracking purposes.
-    std::unique_ptr<scheduler::LayerHistory::LayerHandle> mSchedulerLayerHandle;
-
 private:
     /**
      * Returns an unsorted vector of all layers that are part of this tree.
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 5473db6..8b71728 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -14,183 +14,150 @@
  * limitations under the License.
  */
 
+#undef LOG_TAG
+#define LOG_TAG "LayerHistory"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include "LayerHistory.h"
 
-#include <cinttypes>
-#include <cstdint>
-#include <limits>
-#include <numeric>
-#include <string>
-#include <unordered_map>
-
 #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 "LayerInfo.h"
 #include "SchedulerUtils.h"
 
-namespace android {
-namespace scheduler {
+namespace android::scheduler {
 
-std::atomic<int64_t> LayerHistory::sNextId = 0;
+namespace {
 
-LayerHistory::LayerHistory() {
+bool isLayerActive(const Layer& layer, const LayerInfo& info, nsecs_t threshold) {
+    return layer.isVisible() && (info.isHDR() || info.getLastUpdatedTime() >= threshold);
+}
+
+bool traceEnabled() {
     char value[PROPERTY_VALUE_MAX];
     property_get("debug.sf.layer_history_trace", value, "0");
-    mTraceEnabled = static_cast<bool>(atoi(value));
+    return atoi(value);
 }
 
+void trace(const wp<Layer>& weak, int fps) {
+    const auto layer = weak.promote();
+    if (!layer) return;
+
+    const auto& name = layer->getName();
+    const auto tag = "LFPS " + name;
+    ATRACE_INT(tag.c_str(), fps);
+    ALOGD("%s: %s @ %d Hz", __FUNCTION__, name.c_str(), fps);
+}
+
+} // namespace
+
+LayerHistory::LayerHistory() : mTraceEnabled(traceEnabled()) {}
 LayerHistory::~LayerHistory() = default;
 
-std::unique_ptr<LayerHistory::LayerHandle> LayerHistory::createLayer(const std::string name,
-                                                                     float minRefreshRate,
-                                                                     float maxRefreshRate) {
-    const int64_t id = sNextId++;
-
+void LayerHistory::registerLayer(Layer* layer, float lowRefreshRate, float highRefreshRate) {
+    auto info = std::make_unique<LayerInfo>(lowRefreshRate, highRefreshRate);
     std::lock_guard lock(mLock);
-    mInactiveLayerInfos.emplace(id,
-                                std::make_shared<LayerInfo>(name, minRefreshRate, maxRefreshRate));
-    return std::make_unique<LayerHistory::LayerHandle>(*this, id);
+    mLayerInfos.emplace_back(layer, std::move(info));
 }
 
-void LayerHistory::destroyLayer(const int64_t id) {
+void LayerHistory::record(Layer* layer, nsecs_t presentTime, bool isHDR, nsecs_t now) {
     std::lock_guard lock(mLock);
-    auto it = mActiveLayerInfos.find(id);
-    if (it != mActiveLayerInfos.end()) {
-        mActiveLayerInfos.erase(it);
-    }
 
-    it = mInactiveLayerInfos.find(id);
-    if (it != mInactiveLayerInfos.end()) {
-        mInactiveLayerInfos.erase(it);
+    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);
+    info->setIsHDR(isHDR);
+
+    // Activate layer if inactive.
+    if (const auto end = activeLayers().end(); it >= end) {
+        std::iter_swap(it, end);
+        mActiveLayersEnd++;
     }
 }
 
-void LayerHistory::insert(const std::unique_ptr<LayerHandle>& layerHandle, nsecs_t presentTime,
-                          bool isHdr) {
-    std::shared_ptr<LayerInfo> layerInfo;
-    {
-        std::lock_guard lock(mLock);
-        auto layerInfoIterator = mInactiveLayerInfos.find(layerHandle->mId);
-        if (layerInfoIterator != mInactiveLayerInfos.end()) {
-            layerInfo = layerInfoIterator->second;
-            mInactiveLayerInfos.erase(layerInfoIterator);
-            mActiveLayerInfos.insert({layerHandle->mId, layerInfo});
-        } else {
-            layerInfoIterator = mActiveLayerInfos.find(layerHandle->mId);
-            if (layerInfoIterator != mActiveLayerInfos.end()) {
-                layerInfo = layerInfoIterator->second;
-            } else {
-                ALOGW("Inserting information about layer that is not registered: %" PRId64,
-                      layerHandle->mId);
-                return;
-            }
-        }
-    }
-    layerInfo->setLastPresentTime(presentTime);
-    layerInfo->setHDRContent(isHdr);
-}
-
-void LayerHistory::setVisibility(const std::unique_ptr<LayerHandle>& layerHandle, bool visible) {
-    std::shared_ptr<LayerInfo> layerInfo;
-    {
-        std::lock_guard lock(mLock);
-        auto layerInfoIterator = mInactiveLayerInfos.find(layerHandle->mId);
-        if (layerInfoIterator != mInactiveLayerInfos.end()) {
-            layerInfo = layerInfoIterator->second;
-            if (visible) {
-                mInactiveLayerInfos.erase(layerInfoIterator);
-                mActiveLayerInfos.insert({layerHandle->mId, layerInfo});
-            }
-        } else {
-            layerInfoIterator = mActiveLayerInfos.find(layerHandle->mId);
-            if (layerInfoIterator != mActiveLayerInfos.end()) {
-                layerInfo = layerInfoIterator->second;
-            } else {
-                ALOGW("Inserting information about layer that is not registered: %" PRId64,
-                      layerHandle->mId);
-                return;
-            }
-        }
-    }
-    layerInfo->setVisibility(visible);
-}
-
-std::pair<float, bool> LayerHistory::getDesiredRefreshRateAndHDR() {
+LayerHistory::Summary LayerHistory::summarize(nsecs_t now) {
+    float maxRefreshRate = 0;
     bool isHDR = false;
-    float newRefreshRate = 0.f;
+
     std::lock_guard lock(mLock);
 
-    removeIrrelevantLayers();
+    partitionLayers(now);
 
-    // Iterate through all layers that have been recently updated, and find the max refresh rate.
-    for (const auto& [layerId, layerInfo] : mActiveLayerInfos) {
-        const bool recent = layerInfo->isRecentlyActive();
+    // Find the maximum refresh rate among recently active layers.
+    for (const auto& [layer, info] : activeLayers()) {
+        const bool recent = info->isRecentlyActive(now);
         if (recent || CC_UNLIKELY(mTraceEnabled)) {
-            const float refreshRate = layerInfo->getDesiredRefreshRate();
-            if (recent && refreshRate > newRefreshRate) {
-                newRefreshRate = refreshRate;
+            const float refreshRate = info->getRefreshRate(now);
+            if (recent && refreshRate > maxRefreshRate) {
+                maxRefreshRate = refreshRate;
             }
 
             if (CC_UNLIKELY(mTraceEnabled)) {
-                std::string name = "LFPS " + layerInfo->getName();
-                const float rate = std::round(refreshRate);
-                ATRACE_INT(name.c_str(), rate);
-                ALOGD("%s: %f", name.c_str(), rate);
+                trace(layer, std::round(refreshRate));
             }
         }
-        isHDR |= layerInfo->getHDRContent();
+        isHDR |= info->isHDR();
     }
     if (CC_UNLIKELY(mTraceEnabled)) {
-        ALOGD("LayerHistory DesiredRefreshRate: %.2f", newRefreshRate);
+        ALOGD("%s: maxRefreshRate=%.2f, isHDR=%d", __FUNCTION__, maxRefreshRate, isHDR);
     }
 
-    return {newRefreshRate, isHDR};
+    return {maxRefreshRate, isHDR};
 }
 
-void LayerHistory::removeIrrelevantLayers() {
-    const int64_t obsoleteEpsilon = systemTime() - scheduler::OBSOLETE_TIME_EPSILON_NS.count();
-    // Iterator pointing to first element in map
-    auto it = mActiveLayerInfos.begin();
-    while (it != mActiveLayerInfos.end()) {
-        // If last updated was before the obsolete time, remove it.
-        // Keep HDR layer around as long as they are visible.
-        if (!it->second->isVisible() ||
-            (!it->second->getHDRContent() && it->second->getLastUpdatedTime() < obsoleteEpsilon)) {
-            // erase() function returns the iterator of the next
-            // to last deleted element.
-            if (CC_UNLIKELY(mTraceEnabled)) {
-                ALOGD("Layer %s obsolete", it->second->getName().c_str());
-                // Make sure to update systrace to indicate that the layer was erased.
-                std::string layerName = "LFPS " + it->second->getName();
-                ATRACE_INT(layerName.c_str(), 0);
-            }
-            auto id = it->first;
-            auto layerInfo = it->second;
-            layerInfo->clearHistory();
-            mInactiveLayerInfos.insert({id, layerInfo});
-            it = mActiveLayerInfos.erase(it);
+void LayerHistory::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++;
+            continue;
+        }
+
+        if (CC_UNLIKELY(mTraceEnabled)) {
+            trace(weak, 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 {
-            ++it;
+            std::swap(mLayerInfos[i], mLayerInfos[--end]);
         }
     }
+
+    mLayerInfos.erase(mLayerInfos.begin() + end, mLayerInfos.end());
 }
 
-void LayerHistory::clearHistory() {
+void LayerHistory::clear() {
     std::lock_guard lock(mLock);
 
-    auto it = mActiveLayerInfos.begin();
-    while (it != mActiveLayerInfos.end()) {
-        auto id = it->first;
-        auto layerInfo = it->second;
-        layerInfo->clearHistory();
-        mInactiveLayerInfos.insert({id, layerInfo});
-        it = mActiveLayerInfos.erase(it);
+    for (const auto& [layer, info] : activeLayers()) {
+        info->clearHistory();
     }
+
+    mActiveLayersEnd = 0;
 }
 
-} // namespace scheduler
-} // namespace android
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index 5598cc1..15ac8ca 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -16,76 +16,78 @@
 
 #pragma once
 
-#include <array>
-#include <cinttypes>
-#include <cstdint>
-#include <numeric>
-#include <string>
-#include <unordered_map>
-
+#include <android-base/thread_annotations.h>
+#include <utils/RefBase.h>
 #include <utils/Timers.h>
 
-#include "LayerInfo.h"
-#include "SchedulerUtils.h"
+#include <memory>
+#include <mutex>
+#include <utility>
+#include <vector>
 
 namespace android {
+
+class Layer;
+
 namespace scheduler {
 
-/*
- * This class represents information about layers that are considered current. We keep an
- * unordered map between layer name and LayerInfo.
- */
+class LayerInfo;
+
+// Records per-layer history of scheduling-related information (primarily present time),
+// heuristically categorizes layers as active or inactive, and summarizes stats about
+// active layers (primarily maximum refresh rate). See go/content-fps-detection-in-scheduler.
 class LayerHistory {
 public:
-    // Handle for each layer we keep track of.
-    class LayerHandle {
-    public:
-        LayerHandle(LayerHistory& lh, int64_t id) : mId(id), mLayerHistory(lh) {}
-        ~LayerHandle() { mLayerHistory.destroyLayer(mId); }
-
-        const int64_t mId;
-
-    private:
-        LayerHistory& mLayerHistory;
-    };
-
     LayerHistory();
     ~LayerHistory();
 
-    // When the layer is first created, register it.
-    std::unique_ptr<LayerHandle> createLayer(const std::string name, float minRefreshRate,
-                                             float maxRefreshRate);
+    // Layers are unregistered when the weak reference expires.
+    void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate);
 
-    // Method for inserting layers and their requested present time into the unordered map.
-    void insert(const std::unique_ptr<LayerHandle>& layerHandle, nsecs_t presentTime, bool isHdr);
-    // Method for setting layer visibility
-    void setVisibility(const std::unique_ptr<LayerHandle>& layerHandle, bool visible);
+    // Marks the layer as active, and records the given state to its history.
+    void record(Layer*, nsecs_t presentTime, bool isHDR, nsecs_t now);
 
-    // Returns the desired refresh rate, which is a max refresh rate of all the current
-    // layers. See go/content-fps-detection-in-scheduler for more information.
-    std::pair<float, bool> getDesiredRefreshRateAndHDR();
+    struct Summary {
+        float maxRefreshRate; // Maximum refresh rate among recently active layers.
+        bool isHDR;           // True if any recently active layer has HDR content.
+    };
 
-    // Clears all layer history.
-    void clearHistory();
+    // Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
+    Summary summarize(nsecs_t now);
 
-    // Removes the handle and the object from the map.
-    void destroyLayer(const int64_t id);
+    void clear();
 
 private:
-    // Removes the layers that have been idle for a given amount of time from mLayerInfos.
-    void removeIrrelevantLayers() REQUIRES(mLock);
+    friend class LayerHistoryTest;
 
-    // Information about currently active layers.
-    std::mutex mLock;
-    std::unordered_map<int64_t, std::shared_ptr<LayerInfo>> mActiveLayerInfos GUARDED_BY(mLock);
-    std::unordered_map<int64_t, std::shared_ptr<LayerInfo>> mInactiveLayerInfos GUARDED_BY(mLock);
+    using LayerPair = std::pair<wp<Layer>, std::unique_ptr<LayerInfo>>;
+    using LayerInfos = std::vector<LayerPair>;
 
-    // Each layer has it's own ID. This variable keeps track of the count.
-    static std::atomic<int64_t> sNextId;
+    struct ActiveLayers {
+        LayerInfos& infos;
+        const size_t index;
 
-    // Flag whether to log layer FPS in systrace
-    bool mTraceEnabled = false;
+        auto begin() { return infos.begin(); }
+        auto end() { return begin() + 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;
+
+    // Whether to emit systrace output and debug logs.
+    const bool mTraceEnabled;
 };
 
 } // namespace scheduler
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 723d71f..f3b0d56 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -16,27 +16,17 @@
 
 #include "LayerInfo.h"
 
-#include <cinttypes>
-#include <cstdint>
-#include <numeric>
-#include <string>
+#include <algorithm>
+#include <utility>
 
-namespace android {
-namespace scheduler {
+namespace android::scheduler {
 
-LayerInfo::LayerInfo(const std::string name, float minRefreshRate, float maxRefreshRate)
-      : mName(name),
-        mMinRefreshDuration(1e9f / maxRefreshRate),
-        mLowActivityRefreshDuration(1e9f / minRefreshRate),
-        mRefreshRateHistory(mMinRefreshDuration) {}
+LayerInfo::LayerInfo(float lowRefreshRate, float highRefreshRate)
+      : mLowRefreshRate(lowRefreshRate), mHighRefreshRate(highRefreshRate) {}
 
-LayerInfo::~LayerInfo() = default;
-
-void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime) {
-    std::lock_guard lock(mLock);
-
+void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now) {
     // Buffers can come with a present time far in the future. That keeps them relevant.
-    mLastUpdatedTime = std::max(lastPresentTime, systemTime());
+    mLastUpdatedTime = std::max(lastPresentTime, now);
     mPresentTimeHistory.insertPresentTime(mLastUpdatedTime);
 
     if (mLastPresentTime == 0) {
@@ -45,14 +35,13 @@
         return;
     }
 
-    const nsecs_t timeDiff = lastPresentTime - mLastPresentTime;
+    const nsecs_t period = lastPresentTime - mLastPresentTime;
     mLastPresentTime = lastPresentTime;
     // Ignore time diff that are too high - those are stale values
-    if (timeDiff > OBSOLETE_TIME_EPSILON_NS.count()) return;
-    const nsecs_t refreshDuration = std::max(timeDiff, mMinRefreshDuration);
-    const int fps = 1e9f / refreshDuration;
+    if (period > MAX_ACTIVE_LAYER_PERIOD_NS.count()) return;
+
+    const float fps = std::min(1e9f / period, mHighRefreshRate);
     mRefreshRateHistory.insertRefreshRate(fps);
 }
 
-} // namespace scheduler
-} // namespace android
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 17afdda..b86709f 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -16,27 +16,37 @@
 
 #pragma once
 
-#include <cinttypes>
-#include <cstdint>
-#include <deque>
-#include <mutex>
-#include <numeric>
-#include <string>
-
-#include <log/log.h>
-
-#include <utils/Mutex.h>
 #include <utils/Timers.h>
 
+#include <chrono>
+#include <deque>
+
 #include "SchedulerUtils.h"
 
 namespace android {
+
+class Layer;
+
 namespace scheduler {
 
-/*
- * This class represents information about individial layers.
- */
+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 LayerInfo {
+    // 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;
+
     /**
      * Struct that keeps the information about the refresh rate for last
      * HISTORY_SIZE frames. This is used to better determine the refresh rate
@@ -44,9 +54,9 @@
      */
     class RefreshRateHistory {
     public:
-        explicit RefreshRateHistory(nsecs_t minRefreshDuration)
-              : mMinRefreshDuration(minRefreshDuration) {}
-        void insertRefreshRate(int refreshRate) {
+        explicit RefreshRateHistory(float highRefreshRate) : mHighRefreshRate(highRefreshRate) {}
+
+        void insertRefreshRate(float refreshRate) {
             mElements.push_back(refreshRate);
             if (mElements.size() > HISTORY_SIZE) {
                 mElements.pop_front();
@@ -54,19 +64,16 @@
         }
 
         float getRefreshRateAvg() const {
-            if (mElements.empty()) {
-                return 1e9f / mMinRefreshDuration;
-            }
-
-            return scheduler::calculate_mean(mElements);
+            return mElements.empty() ? mHighRefreshRate : calculate_mean(mElements);
         }
 
         void clearHistory() { mElements.clear(); }
 
     private:
-        std::deque<nsecs_t> mElements;
+        const float mHighRefreshRate;
+
         static constexpr size_t HISTORY_SIZE = 30;
-        const nsecs_t mMinRefreshDuration;
+        std::deque<float> mElements;
     };
 
     /**
@@ -76,6 +83,8 @@
      */
     class PresentTimeHistory {
     public:
+        static constexpr size_t HISTORY_SIZE = 90;
+
         void insertPresentTime(nsecs_t presentTime) {
             mElements.push_back(presentTime);
             if (mElements.size() > HISTORY_SIZE) {
@@ -83,60 +92,45 @@
             }
         }
 
-        // Checks whether the present time that was inserted HISTORY_SIZE ago is within a
-        // certain threshold: TIME_EPSILON_NS.
-        bool isRelevant() const {
+        // Returns whether the earliest present time is within the active threshold.
+        bool isRecentlyActive(nsecs_t now) const {
             if (mElements.size() < 2) {
                 return false;
             }
 
             // The layer had to publish at least HISTORY_SIZE or HISTORY_TIME of updates
-            if (mElements.size() != HISTORY_SIZE &&
-                mElements.at(mElements.size() - 1) - mElements.at(0) < HISTORY_TIME.count()) {
+            if (mElements.size() < HISTORY_SIZE &&
+                mElements.back() - mElements.front() < HISTORY_TIME.count()) {
                 return false;
             }
 
-            // The last update should not be older than OBSOLETE_TIME_EPSILON_NS nanoseconds.
-            const int64_t obsoleteEpsilon =
-                    systemTime() - scheduler::OBSOLETE_TIME_EPSILON_NS.count();
-            if (mElements.at(mElements.size() - 1) < obsoleteEpsilon) {
-                return false;
-            }
-
-            return true;
+            return mElements.back() >= getActiveLayerThreshold(now);
         }
 
-        bool isLowActivityLayer() const {
-            // We want to make sure that we received more than two frames from the layer
-            // in order to check low activity.
-            if (mElements.size() < scheduler::LOW_ACTIVITY_BUFFERS + 1) {
+        bool isFrequent(nsecs_t now) const {
+            // Assume layer is infrequent if too few present times have been recorded.
+            if (mElements.size() < FREQUENT_LAYER_WINDOW_SIZE) {
                 return false;
             }
 
-            const int64_t obsoleteEpsilon =
-                    systemTime() - scheduler::LOW_ACTIVITY_EPSILON_NS.count();
-            // Check the frame before last to determine whether there is low activity.
-            // If that frame is older than LOW_ACTIVITY_EPSILON_NS, the layer is sending
-            // infrequent updates.
-            if (mElements.at(mElements.size() - (scheduler::LOW_ACTIVITY_BUFFERS + 1)) <
-                obsoleteEpsilon) {
-                return true;
-            }
-
-            return false;
+            // Layer is frequent if the earliest value in the window of most recent present times is
+            // within threshold.
+            const auto it = mElements.end() - FREQUENT_LAYER_WINDOW_SIZE;
+            const nsecs_t threshold = now - MAX_FREQUENT_LAYER_PERIOD_NS.count();
+            return *it >= threshold;
         }
 
         void clearHistory() { mElements.clear(); }
 
     private:
         std::deque<nsecs_t> mElements;
-        static constexpr size_t HISTORY_SIZE = 90;
         static constexpr std::chrono::nanoseconds HISTORY_TIME = 1s;
     };
 
+    friend class LayerHistoryTest;
+
 public:
-    LayerInfo(const std::string name, float minRefreshRate, float maxRefreshRate);
-    ~LayerInfo();
+    LayerInfo(float lowRefreshRate, float highRefreshRate);
 
     LayerInfo(const LayerInfo&) = delete;
     LayerInfo& operator=(const LayerInfo&) = delete;
@@ -144,71 +138,37 @@
     // Records the last requested oresent 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);
+    void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now);
 
-    void setHDRContent(bool isHdr) {
-        std::lock_guard lock(mLock);
-        mIsHDR = isHdr;
-    }
+    bool isHDR() const { return mIsHDR; }
+    void setIsHDR(bool isHDR) { mIsHDR = isHDR; }
 
-    void setVisibility(bool visible) {
-        std::lock_guard lock(mLock);
-        mIsVisible = visible;
-    }
+    bool isRecentlyActive(nsecs_t now) const { return mPresentTimeHistory.isRecentlyActive(now); }
+    bool isFrequent(nsecs_t now) const { return mPresentTimeHistory.isFrequent(now); }
 
-    // Checks the present time history to see whether the layer is relevant.
-    bool isRecentlyActive() const {
-        std::lock_guard lock(mLock);
-        return mPresentTimeHistory.isRelevant();
-    }
-
-    // Calculate the average refresh rate.
-    float getDesiredRefreshRate() const {
-        std::lock_guard lock(mLock);
-
-        if (mPresentTimeHistory.isLowActivityLayer()) {
-            return 1e9f / mLowActivityRefreshDuration;
-        }
-        return mRefreshRateHistory.getRefreshRateAvg();
-    }
-
-    bool getHDRContent() {
-        std::lock_guard lock(mLock);
-        return mIsHDR;
-    }
-
-    bool isVisible() {
-        std::lock_guard lock(mLock);
-        return mIsVisible;
+    float getRefreshRate(nsecs_t now) const {
+        return isFrequent(now) ? mRefreshRateHistory.getRefreshRateAvg() : mLowRefreshRate;
     }
 
     // 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() {
-        std::lock_guard lock(mLock);
-        return mLastUpdatedTime;
-    }
-
-    std::string getName() const { return mName; }
+    nsecs_t getLastUpdatedTime() const { return mLastUpdatedTime; }
 
     void clearHistory() {
-        std::lock_guard lock(mLock);
         mRefreshRateHistory.clearHistory();
         mPresentTimeHistory.clearHistory();
     }
 
 private:
-    const std::string mName;
-    const nsecs_t mMinRefreshDuration;
-    const nsecs_t mLowActivityRefreshDuration;
-    mutable std::mutex mLock;
-    nsecs_t mLastUpdatedTime GUARDED_BY(mLock) = 0;
-    nsecs_t mLastPresentTime GUARDED_BY(mLock) = 0;
-    RefreshRateHistory mRefreshRateHistory GUARDED_BY(mLock);
-    PresentTimeHistory mPresentTimeHistory GUARDED_BY(mLock);
-    bool mIsHDR GUARDED_BY(mLock) = false;
-    bool mIsVisible GUARDED_BY(mLock) = false;
+    const float mLowRefreshRate;
+    const float mHighRefreshRate;
+
+    nsecs_t mLastUpdatedTime = 0;
+    nsecs_t mLastPresentTime = 0;
+    RefreshRateHistory mRefreshRateHistory{mHighRefreshRate};
+    PresentTimeHistory mPresentTimeHistory;
+    bool mIsHDR = false;
 };
 
 } // namespace scheduler
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index d60e101..71b3500 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -20,13 +20,6 @@
 
 #include "Scheduler.h"
 
-#include <algorithm>
-#include <cinttypes>
-#include <cstdint>
-#include <functional>
-#include <memory>
-#include <numeric>
-
 #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
 #include <android/hardware/configstore/1.1/ISurfaceFlingerConfigs.h>
 #include <configstore/Utils.h>
@@ -37,6 +30,14 @@
 #include <utils/Timers.h>
 #include <utils/Trace.h>
 
+#include <algorithm>
+#include <cinttypes>
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <numeric>
+
+#include "../Layer.h"
 #include "DispSync.h"
 #include "DispSyncSource.h"
 #include "EventControlThread.h"
@@ -325,37 +326,27 @@
     return mPrimaryDispSync->expectedPresentTime();
 }
 
-std::unique_ptr<scheduler::LayerHistory::LayerHandle> Scheduler::registerLayer(
-        std::string const& name, int windowType) {
+void Scheduler::registerLayer(Layer* layer) {
     uint32_t defaultFps, performanceFps;
     if (mRefreshRateConfigs.refreshRateSwitchingSupported()) {
         defaultFps = mRefreshRateConfigs.getRefreshRateFromType(RefreshRateType::DEFAULT).fps;
-        performanceFps =
-                mRefreshRateConfigs
-                        .getRefreshRateFromType((windowType == InputWindowInfo::TYPE_WALLPAPER)
-                                                        ? RefreshRateType::DEFAULT
-                                                        : RefreshRateType::PERFORMANCE)
-                        .fps;
+        const auto type = layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER
+                ? RefreshRateType::DEFAULT
+                : RefreshRateType::PERFORMANCE;
+        performanceFps = mRefreshRateConfigs.getRefreshRateFromType(type).fps;
     } else {
         defaultFps = mRefreshRateConfigs.getCurrentRefreshRate().second.fps;
         performanceFps = defaultFps;
     }
-    return mLayerHistory.createLayer(name, defaultFps, performanceFps);
+    mLayerHistory.registerLayer(layer, defaultFps, performanceFps);
 }
 
-void Scheduler::addLayerPresentTimeAndHDR(
-        const std::unique_ptr<scheduler::LayerHistory::LayerHandle>& layerHandle,
-        nsecs_t presentTime, bool isHDR) {
-    mLayerHistory.insert(layerHandle, presentTime, isHDR);
-}
-
-void Scheduler::setLayerVisibility(
-        const std::unique_ptr<scheduler::LayerHistory::LayerHandle>& layerHandle, bool visible) {
-    mLayerHistory.setVisibility(layerHandle, visible);
+void Scheduler::recordLayerHistory(Layer* layer, nsecs_t presentTime, bool isHDR) {
+    mLayerHistory.record(layer, presentTime, isHDR, systemTime());
 }
 
 void Scheduler::updateFpsBasedOnContent() {
-    auto [refreshRate, isHDR] = mLayerHistory.getDesiredRefreshRateAndHDR();
+    auto [refreshRate, isHDR] = mLayerHistory.summarize(systemTime());
     const uint32_t refreshRateRound = std::round(refreshRate);
     RefreshRateType newRefreshRateType;
     {
@@ -402,7 +393,7 @@
 
     // Touch event will boost the refresh rate to performance.
     // Clear Layer History to get fresh FPS detection
-    mLayerHistory.clearHistory();
+    mLayerHistory.clear();
 }
 
 void Scheduler::setDisplayPowerState(bool normal) {
@@ -417,7 +408,7 @@
 
     // Display Power event will boost the refresh rate to performance.
     // Clear Layer History to get fresh FPS detection
-    mLayerHistory.clearHistory();
+    mLayerHistory.clear();
 }
 
 void Scheduler::kernelIdleTimerCallback(TimerState state) {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index a5971fe..c983475 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -100,17 +100,11 @@
     void addPresentFence(const std::shared_ptr<FenceTime>&);
     void setIgnorePresentFences(bool ignore);
     nsecs_t getDispSyncExpectedPresentTime();
-    // Registers the layer in the scheduler, and returns the handle for future references.
-    std::unique_ptr<scheduler::LayerHistory::LayerHandle> registerLayer(std::string const& name,
-                                                                        int windowType);
 
-    // Stores present time for a layer.
-    void addLayerPresentTimeAndHDR(
-            const std::unique_ptr<scheduler::LayerHistory::LayerHandle>& layerHandle,
-            nsecs_t presentTime, bool isHDR);
-    // Stores visibility for a layer.
-    void setLayerVisibility(
-            const std::unique_ptr<scheduler::LayerHistory::LayerHandle>& layerHandle, bool visible);
+    // Layers are registered on creation, and unregistered when the weak reference expires.
+    void registerLayer(Layer*);
+    void recordLayerHistory(Layer*, nsecs_t presentTime, bool isHDR);
+
     // Updates FPS based on the most content presented.
     void updateFpsBasedOnContent();
 
diff --git a/services/surfaceflinger/Scheduler/SchedulerUtils.h b/services/surfaceflinger/Scheduler/SchedulerUtils.h
index 3b7567c..d301b99 100644
--- a/services/surfaceflinger/Scheduler/SchedulerUtils.h
+++ b/services/surfaceflinger/Scheduler/SchedulerUtils.h
@@ -16,7 +16,6 @@
 
 #pragma once
 
-#include <chrono>
 #include <cinttypes>
 #include <numeric>
 #include <unordered_map>
@@ -38,21 +37,6 @@
     return lhs.id == rhs.id;
 }
 
-using namespace std::chrono_literals;
-
-// This number is used when we try to determine how long do we keep layer information around
-// before we remove it. It is also used to determine how long the layer stays relevant.
-// This time period captures infrequent updates when playing YouTube video with static image,
-// or waiting idle in messaging app, when cursor is blinking.
-static constexpr std::chrono::nanoseconds OBSOLETE_TIME_EPSILON_NS = 1200ms;
-
-// Layer is considered low activity if the LOW_ACTIVITY_BUFFERS buffers come more than
-// LOW_ACTIVITY_EPSILON_NS  apart.
-// This is helping SF to vote for lower refresh rates when there is not activity
-// in screen.
-static constexpr int LOW_ACTIVITY_BUFFERS = 2;
-static constexpr std::chrono::nanoseconds LOW_ACTIVITY_EPSILON_NS = 250ms;
-
 // Calculates the statistical mean (average) in the data structure (array, vector). The
 // function does not modify the contents of the array.
 template <typename T>
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 057669b..1acb2da 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -5505,6 +5505,16 @@
     return nullptr;
 }
 
+void SurfaceFlinger::onLayerCreated(Layer* layer) {
+    mNumLayers++;
+    mScheduler->registerLayer(layer);
+}
+
+void SurfaceFlinger::onLayerDestroyed(Layer* layer) {
+    mNumLayers--;
+    mOffscreenLayers.erase(layer);
+}
+
 void SurfaceFlinger::bufferErased(const client_cache_t& clientCacheId) {
     getRenderEngine().unbindExternalTextureBuffer(clientCacheId.id);
 }
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index b719245..abb8b82 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -310,11 +310,8 @@
     bool authenticateSurfaceTextureLocked(
         const sp<IGraphicBufferProducer>& bufferProducer) const;
 
-    inline void onLayerCreated() { mNumLayers++; }
-    inline void onLayerDestroyed(Layer* layer) {
-        mNumLayers--;
-        mOffscreenLayers.erase(layer);
-    }
+    void onLayerCreated(Layer*);
+    void onLayerDestroyed(Layer*);
 
     TransactionCompletedThread& getTransactionCompletedThread() {
         return mTransactionCompletedThread;
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 8e7440c..9a962bc 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -1,140 +1,243 @@
 #undef LOG_TAG
-#define LOG_TAG "LayerHistoryUnittests"
+#define LOG_TAG "LayerHistoryTest"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
-
 #include <log/log.h>
 
-#include <mutex>
-#include <thread>
-
 #include "Scheduler/LayerHistory.h"
+#include "Scheduler/LayerInfo.h"
+#include "TestableScheduler.h"
+#include "TestableSurfaceFlinger.h"
+#include "mock/MockLayer.h"
 
 using testing::_;
 using testing::Return;
 
-namespace android {
-namespace scheduler {
+namespace android::scheduler {
 
 class LayerHistoryTest : public testing::Test {
-public:
-    LayerHistoryTest();
-    ~LayerHistoryTest() override;
-
 protected:
-    std::unique_ptr<LayerHistory> mLayerHistory;
+    static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfo::PresentTimeHistory::HISTORY_SIZE;
+    static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS = LayerInfo::MAX_FREQUENT_LAYER_PERIOD_NS;
 
-    static constexpr float MIN_REFRESH_RATE = 30.f;
-    static constexpr float MAX_REFRESH_RATE = 90.f;
-    static constexpr auto RELEVANT_FRAME_THRESHOLD = 90u;
-    static constexpr uint64_t THIRTY_FPS_INTERVAL = 33'333'333;
+    static constexpr float LO_FPS = 30.f;
+    static constexpr nsecs_t LO_FPS_PERIOD = 33'333'333;
 
-    void forceRelevancy(const std::unique_ptr<LayerHistory::LayerHandle>& testLayer) {
-        mLayerHistory->setVisibility(testLayer, true);
-        for (auto i = 0u; i < RELEVANT_FRAME_THRESHOLD; i++) {
-            mLayerHistory->insert(testLayer, 0, false /*isHDR*/);
-        }
-    };
-};
+    static constexpr float HI_FPS = 90.f;
+    static constexpr nsecs_t HI_FPS_PERIOD = 11'111'111;
 
-LayerHistoryTest::LayerHistoryTest() {
-    mLayerHistory = std::make_unique<LayerHistory>();
-}
-LayerHistoryTest::~LayerHistoryTest() {}
+    LayerHistoryTest() { mFlinger.resetScheduler(mScheduler); }
 
-namespace {
-TEST_F(LayerHistoryTest, oneLayer) {
-    std::unique_ptr<LayerHistory::LayerHandle> testLayer =
-            mLayerHistory->createLayer("TestLayer", MIN_REFRESH_RATE, MAX_REFRESH_RATE);
-    mLayerHistory->setVisibility(testLayer, true);
-    for (auto i = 0u; i < RELEVANT_FRAME_THRESHOLD; i++) {
-        EXPECT_FLOAT_EQ(0.f, mLayerHistory->getDesiredRefreshRateAndHDR().first);
-        mLayerHistory->insert(testLayer, 0, false /*isHDR*/);
+    LayerHistory& history() { return mScheduler->mutableLayerHistory(); }
+    const LayerHistory& history() const { return mScheduler->mutableLayerHistory(); }
+
+    size_t layerCount() const NO_THREAD_SAFETY_ANALYSIS { return history().mLayerInfos.size(); }
+    size_t activeLayerCount() const NO_THREAD_SAFETY_ANALYSIS { return history().mActiveLayersEnd; }
+
+    size_t frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
+        const auto& infos = history().mLayerInfos;
+        return std::count_if(infos.begin(), infos.begin() + history().mActiveLayersEnd,
+                             [now](const auto& pair) { return pair.second->isFrequent(now); });
     }
 
-    // Add a few more. This time we should get MAX refresh rate as the layer
-    // becomes relevant
-    static constexpr auto A_FEW = 10;
-    for (auto i = 0u; i < A_FEW; i++) {
-        EXPECT_FLOAT_EQ(MAX_REFRESH_RATE, mLayerHistory->getDesiredRefreshRateAndHDR().first);
-        mLayerHistory->insert(testLayer, 0, false /*isHDR*/);
+    auto createLayer() { return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger())); }
+
+    RefreshRateConfigs mConfigs{true,
+                                {RefreshRateConfigs::InputConfig{0, LO_FPS_PERIOD},
+                                 RefreshRateConfigs::InputConfig{1, HI_FPS_PERIOD}},
+                                0};
+    TestableScheduler* const mScheduler{new TestableScheduler(mConfigs)};
+    TestableSurfaceFlinger mFlinger;
+
+    const nsecs_t mTime = systemTime();
+};
+
+namespace {
+
+TEST_F(LayerHistoryTest, oneLayer) {
+    const auto layer = createLayer();
+    constexpr bool isHDR = false;
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+
+    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);
+    EXPECT_EQ(0, activeLayerCount());
+
+    // 0 FPS is returned if active layers have insufficient history.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
+        history().record(layer.get(), 0, isHDR, mTime);
+        EXPECT_FLOAT_EQ(0, history().summarize(mTime).maxRefreshRate);
+        EXPECT_EQ(1, activeLayerCount());
+    }
+
+    // High FPS is returned once enough history has been recorded.
+    for (int i = 0; i < 10; i++) {
+        history().record(layer.get(), 0, isHDR, mTime);
+        EXPECT_FLOAT_EQ(HI_FPS, history().summarize(mTime).maxRefreshRate);
+        EXPECT_EQ(1, activeLayerCount());
     }
 }
 
 TEST_F(LayerHistoryTest, oneHDRLayer) {
-    std::unique_ptr<LayerHistory::LayerHandle> testLayer =
-            mLayerHistory->createLayer("TestHDRLayer", MIN_REFRESH_RATE, MAX_REFRESH_RATE);
-    mLayerHistory->setVisibility(testLayer, true);
+    const auto layer = createLayer();
+    constexpr bool isHDR = true;
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
 
-    mLayerHistory->insert(testLayer, 0, true /*isHDR*/);
-    EXPECT_FLOAT_EQ(0.0f, mLayerHistory->getDesiredRefreshRateAndHDR().first);
-    EXPECT_EQ(true, mLayerHistory->getDesiredRefreshRateAndHDR().second);
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
 
-    mLayerHistory->setVisibility(testLayer, false);
-    EXPECT_FLOAT_EQ(0.0f, mLayerHistory->getDesiredRefreshRateAndHDR().first);
-    EXPECT_EQ(false, mLayerHistory->getDesiredRefreshRateAndHDR().second);
+    history().record(layer.get(), 0, isHDR, mTime);
+    auto summary = history().summarize(mTime);
+    EXPECT_FLOAT_EQ(0, summary.maxRefreshRate);
+    EXPECT_TRUE(summary.isHDR);
+    EXPECT_EQ(1, activeLayerCount());
+
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
+
+    summary = history().summarize(mTime);
+    EXPECT_FLOAT_EQ(0, summary.maxRefreshRate);
+    EXPECT_FALSE(summary.isHDR);
+    EXPECT_EQ(0, activeLayerCount());
 }
 
 TEST_F(LayerHistoryTest, explicitTimestamp) {
-    std::unique_ptr<LayerHistory::LayerHandle> test30FpsLayer =
-            mLayerHistory->createLayer("30FpsLayer", MIN_REFRESH_RATE, MAX_REFRESH_RATE);
-    mLayerHistory->setVisibility(test30FpsLayer, true);
+    const auto layer = createLayer();
+    constexpr bool isHDR = false;
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
 
-    nsecs_t startTime = systemTime();
-    for (int i = 0; i < RELEVANT_FRAME_THRESHOLD; i++) {
-        mLayerHistory->insert(test30FpsLayer, startTime + (i * THIRTY_FPS_INTERVAL),
-                              false /*isHDR*/);
+    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, isHDR, time);
+        time += LO_FPS_PERIOD;
     }
 
-    EXPECT_FLOAT_EQ(30.f, mLayerHistory->getDesiredRefreshRateAndHDR().first);
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(mTime).maxRefreshRate);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
 }
 
 TEST_F(LayerHistoryTest, multipleLayers) {
-    std::unique_ptr<LayerHistory::LayerHandle> testLayer =
-            mLayerHistory->createLayer("TestLayer", MIN_REFRESH_RATE, MAX_REFRESH_RATE);
-    mLayerHistory->setVisibility(testLayer, true);
-    std::unique_ptr<LayerHistory::LayerHandle> test30FpsLayer =
-            mLayerHistory->createLayer("30FpsLayer", MIN_REFRESH_RATE, MAX_REFRESH_RATE);
-    mLayerHistory->setVisibility(test30FpsLayer, true);
-    std::unique_ptr<LayerHistory::LayerHandle> testLayer2 =
-            mLayerHistory->createLayer("TestLayer2", MIN_REFRESH_RATE, MAX_REFRESH_RATE);
-    mLayerHistory->setVisibility(testLayer2, true);
+    auto layer1 = createLayer();
+    auto layer2 = createLayer();
+    auto layer3 = createLayer();
+    constexpr bool isHDR = false;
 
-    nsecs_t startTime = systemTime();
-    for (int i = 0; i < RELEVANT_FRAME_THRESHOLD; i++) {
-        mLayerHistory->insert(testLayer, 0, false /*isHDR*/);
+    EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer2, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer3, isVisible()).WillRepeatedly(Return(true));
+
+    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, isHDR, time);
+        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
     }
-    EXPECT_FLOAT_EQ(MAX_REFRESH_RATE, mLayerHistory->getDesiredRefreshRateAndHDR().first);
 
-    startTime = systemTime();
-    for (int i = 0; i < RELEVANT_FRAME_THRESHOLD; i++) {
-        mLayerHistory->insert(test30FpsLayer, startTime + (i * THIRTY_FPS_INTERVAL),
-                              false /*isHDR*/);
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time).maxRefreshRate);
+    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, isHDR, time);
+        time += HI_FPS_PERIOD;
     }
-    EXPECT_FLOAT_EQ(MAX_REFRESH_RATE, mLayerHistory->getDesiredRefreshRateAndHDR().first);
 
-    for (int i = 10; i < RELEVANT_FRAME_THRESHOLD; i++) {
-        mLayerHistory->insert(test30FpsLayer, startTime + (i * THIRTY_FPS_INTERVAL),
-                              false /*isHDR*/);
+    // layer1 is still active but infrequent.
+    history().record(layer1.get(), time, isHDR, time);
+
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time).maxRefreshRate);
+    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, isHDR, time);
+        time += LO_FPS_PERIOD;
     }
-    EXPECT_FLOAT_EQ(MAX_REFRESH_RATE, mLayerHistory->getDesiredRefreshRateAndHDR().first);
 
-    // This frame is only around for 9 occurrences, so it doesn't throw
-    // anything off.
-    for (int i = 0; i < 9; i++) {
-        mLayerHistory->insert(testLayer2, 0, false /*isHDR*/);
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time).maxRefreshRate);
+    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, isHDR, time);
+        }
+
+        history().record(layer3.get(), time, isHDR, time);
+        time += HI_FPS_PERIOD;
     }
-    EXPECT_FLOAT_EQ(MAX_REFRESH_RATE, mLayerHistory->getDesiredRefreshRateAndHDR().first);
-    // After 1200 ms frames become obsolete.
-    std::this_thread::sleep_for(std::chrono::milliseconds(1500));
 
-    mLayerHistory->insert(test30FpsLayer,
-                          startTime + (RELEVANT_FRAME_THRESHOLD * THIRTY_FPS_INTERVAL),
-                          false /*isHDR*/);
-    EXPECT_FLOAT_EQ(30.f, mLayerHistory->getDesiredRefreshRateAndHDR().first);
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time).maxRefreshRate);
+    EXPECT_EQ(2, activeLayerCount());
+    EXPECT_EQ(2, frequentLayerCount(time));
+
+    // layer3 becomes recently active.
+    history().record(layer3.get(), time, isHDR, time);
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time).maxRefreshRate);
+    EXPECT_EQ(2, activeLayerCount());
+    EXPECT_EQ(2, frequentLayerCount(time));
+
+    // layer1 expires.
+    layer1.clear();
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time).maxRefreshRate);
+    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, isHDR, time);
+        time += LO_FPS_PERIOD;
+    }
+
+    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time).maxRefreshRate);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer2 expires.
+    layer2.clear();
+    EXPECT_FLOAT_EQ(0, history().summarize(time).maxRefreshRate);
+    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, isHDR, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time).maxRefreshRate);
+    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);
+    EXPECT_EQ(0, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
 }
 
 } // namespace
-} // namespace scheduler
-} // namespace android
\ No newline at end of file
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index ae72467..ae6aa89 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -48,6 +48,7 @@
     auto& mutableEventControlThread() { return mEventControlThread; }
     auto& mutablePrimaryDispSync() { return mPrimaryDispSync; }
     auto& mutableHWVsyncAvailable() { return mHWVsyncAvailable; }
+    auto& mutableLayerHistory() { return mLayerHistory; }
 
     ~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 c7664fb..94fc5f7 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -22,6 +22,7 @@
 #include <compositionengine/OutputLayer.h>
 #include <compositionengine/impl/CompositionEngine.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
+#include <compositionengine/mock/DisplaySurface.h>
 
 #include "BufferQueueLayer.h"
 #include "BufferStateLayer.h"
@@ -173,6 +174,7 @@
 
 class TestableSurfaceFlinger {
 public:
+    SurfaceFlinger* flinger() { return mFlinger.get(); }
     TestableScheduler* scheduler() { return mScheduler; }
 
     // Extend this as needed for accessing SurfaceFlinger private (and public)
@@ -207,13 +209,15 @@
 
         mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
         mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
+        resetScheduler(mScheduler);
 
-        mFlinger->mScheduler.reset(mScheduler);
         mFlinger->mVSyncModulator.emplace(*mScheduler, mFlinger->mAppConnectionHandle,
                                           mFlinger->mSfConnectionHandle,
                                           mFlinger->mPhaseOffsets->getCurrentOffsets());
     }
 
+    void resetScheduler(Scheduler* scheduler) { mFlinger->mScheduler.reset(scheduler); }
+
     using CreateBufferQueueFunction = surfaceflinger::test::Factory::CreateBufferQueueFunction;
     void setCreateBufferQueueFunction(CreateBufferQueueFunction f) {
         mFactory.mCreateBufferQueue = f;
diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
new file mode 100644
index 0000000..f375e23
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include "Layer.h"
+
+namespace android::mock {
+
+class MockLayer : public Layer {
+public:
+    explicit MockLayer(SurfaceFlinger* flinger)
+          : Layer(LayerCreationArgs(flinger, nullptr, "TestLayer", 800, 600, 0, {})) {}
+
+    MOCK_CONST_METHOD0(getType, const char*());
+    MOCK_CONST_METHOD0(isVisible, bool());
+    MOCK_METHOD0(createClone, sp<Layer>());
+};
+
+} // namespace android::mock