Pass fps data to flattener

Use this extra data in determining if a layer should be considered inactive for flattening.
The effect is that sub 1 fps layers are cached immediately upon updating.

Test: Unit tests added, and confirmation of the intended effect on R6 during camera recording using
dumpsys surfaceflinger (expected all CameraLauncher layers to be device composited)

Bug: b/192271493

Change-Id: I06f2dd0b3256da5699ffca7347285dc8cf52713c
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index a000661..36594ea 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -200,6 +200,9 @@
     // The output-independent frame for the cursor
     Rect cursorFrame;
 
+    // framerate of the layer as measured by LayerHistory
+    float fps;
+
     virtual ~LayerFECompositionState();
 
     // Debugging
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
index cff6527..c132041 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
@@ -87,6 +87,13 @@
         const bool mEnableHolePunch;
     };
 
+    // Constants not yet backed by a sysprop
+    // CachedSets that contain no more than this many layers may be considered inactive on the basis
+    // of FPS.
+    static constexpr int kNumLayersFpsConsideration = 1;
+    // Frames/Second threshold below which these CachedSets may be considered inactive.
+    static constexpr float kFpsActiveThreshold = 1.f;
+
     Flattener(renderengine::RenderEngine& renderEngine, const Tunables& tunables);
 
     void setDisplaySize(ui::Size size) {
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
index 5237527..7397c19 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
@@ -245,6 +245,7 @@
     bool isProtected() const {
         return getOutputLayer()->getLayerFE().getCompositionState()->hasProtectedContent;
     }
+    float getFps() const { return getOutputLayer()->getLayerFE().getCompositionState()->fps; }
 
     void dump(std::string& result) const;
     std::optional<std::string> compare(const LayerState& other) const;
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index 2272099..8a476c5 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -409,10 +409,19 @@
     bool runHasFirstLayer = false;
 
     for (auto currentSet = mLayers.cbegin(); currentSet != mLayers.cend(); ++currentSet) {
-        const bool layerIsInactive =
-                now - currentSet->getLastUpdate() > mTunables.mActiveLayerTimeout;
+        bool layerIsInactive = now - currentSet->getLastUpdate() > mTunables.mActiveLayerTimeout;
         const bool layerHasBlur = currentSet->hasBlurBehind();
 
+        // Layers should also be considered inactive whenever their framerate is lower than 1fps.
+        if (!layerIsInactive && currentSet->getLayerCount() == kNumLayersFpsConsideration) {
+            auto layerFps = currentSet->getFirstLayer().getState()->getFps();
+            if (layerFps > 0 && layerFps <= kFpsActiveThreshold) {
+                ATRACE_FORMAT("layer is considered inactive due to low FPS [%s] %f",
+                              currentSet->getFirstLayer().getName().c_str(), layerFps);
+                layerIsInactive = true;
+            }
+        }
+
         if (layerIsInactive && (firstLayer || runHasFirstLayer || !layerHasBlur) &&
             !currentSet->hasUnsupportedDataspace()) {
             if (isPartOfRun) {
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index 9b0a75f..6fbcc75 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -224,6 +224,22 @@
     mFlattener->renderCachedSets(mOutputState, std::nullopt);
 }
 
+TEST_F(FlattenerTest, flattenLayers_ActiveLayersWithLowFpsAreFlattened) {
+    mTestLayers[0]->layerFECompositionState.fps = Flattener::kFpsActiveThreshold / 2;
+    mTestLayers[1]->layerFECompositionState.fps = Flattener::kFpsActiveThreshold;
+
+    auto& layerState1 = mTestLayers[0]->layerState;
+    auto& layerState2 = mTestLayers[1]->layerState;
+
+    const std::vector<const LayerState*> layers = {
+            layerState1.get(),
+            layerState2.get(),
+    };
+
+    initializeFlattener(layers);
+    expectAllLayersFlattened(layers);
+}
+
 TEST_F(FlattenerTest, flattenLayers_basicFlatten) {
     auto& layerState1 = mTestLayers[0]->layerState;
     auto& layerState2 = mTestLayers[1]->layerState;
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index dfb99cc..4606746 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -418,6 +418,8 @@
 
     compositionState->blendMode = static_cast<Hwc2::IComposerClient::BlendMode>(blendMode);
     compositionState->alpha = alpha;
+    compositionState->backgroundBlurRadius = drawingState.backgroundBlurRadius;
+    compositionState->blurRegions = drawingState.blurRegions;
     compositionState->stretchEffect = getStretchEffect();
 }
 
@@ -482,6 +484,11 @@
     // If there are no visible region changes, we still need to update blur parameters.
     compositionState->blurRegions = drawingState.blurRegions;
     compositionState->backgroundBlurRadius = drawingState.backgroundBlurRadius;
+
+    // Layer framerate is used in caching decisions.
+    // Retrieve it from the scheduler which maintains an instance of LayerHistory, and store it in
+    // LayerFECompositionState where it would be visible to Flattener.
+    compositionState->fps = mFlinger->getLayerFramerate(systemTime(), getSequence());
 }
 
 void Layer::prepareCursorCompositionState() {
@@ -932,7 +939,6 @@
     setTransactionFlags(eTransactionNeeded);
     return true;
 }
-
 bool Layer::setMatrix(const layer_state_t::matrix22_t& matrix,
         bool allowNonRectPreservingTransforms) {
     ui::Transform t;
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 84e3548..74a2ca7 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -84,42 +84,38 @@
 
 void LayerHistory::registerLayer(Layer* layer, LayerVoteType type) {
     std::lock_guard lock(mLock);
-    for (const auto& info : mLayerInfos) {
-        LOG_ALWAYS_FATAL_IF(info.first == layer, "%s already registered", layer->getName().c_str());
-    }
+    LOG_ALWAYS_FATAL_IF(findLayer(layer->getSequence()).first !=
+                                LayerHistory::layerStatus::NotFound,
+                        "%s already registered", layer->getName().c_str());
     auto info = std::make_unique<LayerInfo>(layer->getName(), layer->getOwnerUid(), type);
-    mLayerInfos.emplace_back(layer, std::move(info));
+
+    // The layer can be placed on either map, it is assumed that partitionLayers() will be called
+    // to correct them.
+    mInactiveLayerInfos.insert({layer->getSequence(), std::make_pair(layer, std::move(info))});
 }
 
 void LayerHistory::deregisterLayer(Layer* layer) {
     std::lock_guard lock(mLock);
-
-    const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),
-                                 [layer](const auto& pair) { return pair.first == layer; });
-    LOG_ALWAYS_FATAL_IF(it == mLayerInfos.end(), "%s: unknown layer %p", __FUNCTION__, layer);
-
-    const size_t i = static_cast<size_t>(it - mLayerInfos.begin());
-    if (i < mActiveLayersEnd) {
-        mActiveLayersEnd--;
+    if (!mActiveLayerInfos.erase(layer->getSequence())) {
+        if (!mInactiveLayerInfos.erase(layer->getSequence())) {
+            LOG_ALWAYS_FATAL("%s: unknown layer %p", __FUNCTION__, layer);
+        }
     }
-    const size_t last = mLayerInfos.size() - 1;
-    std::swap(mLayerInfos[i], mLayerInfos[last]);
-    mLayerInfos.erase(mLayerInfos.begin() + static_cast<long>(last));
 }
 
 void LayerHistory::record(Layer* layer, nsecs_t presentTime, nsecs_t now,
                           LayerUpdateType updateType) {
     std::lock_guard lock(mLock);
+    auto id = layer->getSequence();
 
-    const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),
-                                 [layer](const auto& pair) { return pair.first == layer; });
-    if (it == mLayerInfos.end()) {
+    auto [found, layerPair] = findLayer(id);
+    if (found == LayerHistory::layerStatus::NotFound) {
         // Offscreen layer
         ALOGV("LayerHistory::record: %s not registered", layer->getName().c_str());
         return;
     }
 
-    const auto& info = it->second;
+    const auto& info = layerPair->second;
     const auto layerProps = LayerInfo::LayerProps{
             .visible = layer->isVisible(),
             .bounds = layer->getBounds(),
@@ -131,9 +127,10 @@
     info->setLastPresentTime(presentTime, now, updateType, mModeChangePending, layerProps);
 
     // Activate layer if inactive.
-    if (const auto end = activeLayers().end(); it >= end) {
-        std::iter_swap(it, end);
-        mActiveLayersEnd++;
+    if (found == LayerHistory::layerStatus::LayerInInactiveMap) {
+        mActiveLayerInfos.insert(
+                {id, std::make_pair(layerPair->first, std::move(layerPair->second))});
+        mInactiveLayerInfos.erase(id);
     }
 }
 
@@ -145,7 +142,8 @@
 
     partitionLayers(now);
 
-    for (const auto& [layer, info] : activeLayers()) {
+    for (const auto& [key, value] : mActiveLayerInfos) {
+        auto& info = value.second;
         const auto frameRateSelectionPriority = info->getFrameRateSelectionPriority();
         const auto layerFocused = Layer::isLayerFocusedBasedOnPriority(frameRateSelectionPriority);
         ALOGV("%s has priority: %d %s focused", info->getName().c_str(), frameRateSelectionPriority,
@@ -179,12 +177,29 @@
 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& [layerUnsafe, info] = mLayerInfos[i];
+    // iterate over inactive map
+    LayerInfos::iterator it = mInactiveLayerInfos.begin();
+    while (it != mInactiveLayerInfos.end()) {
+        auto& [layerUnsafe, info] = it->second;
         if (isLayerActive(*info, threshold)) {
-            i++;
+            // move this to the active map
+
+            mActiveLayerInfos.insert({it->first, std::move(it->second)});
+            it = mInactiveLayerInfos.erase(it);
+        } else {
+            if (CC_UNLIKELY(mTraceEnabled)) {
+                trace(*info, LayerHistory::LayerVoteType::NoVote, 0);
+            }
+            info->onLayerInactive(now);
+            it++;
+        }
+    }
+
+    // iterate over active map
+    it = mActiveLayerInfos.begin();
+    while (it != mActiveLayerInfos.end()) {
+        auto& [layerUnsafe, info] = it->second;
+        if (isLayerActive(*info, threshold)) {
             // Set layer vote if set
             const auto frameRate = info->getSetFrameRateVote();
             const auto voteType = [&]() {
@@ -206,30 +221,68 @@
             } else {
                 info->resetLayerVote();
             }
-            continue;
-        }
 
-        if (CC_UNLIKELY(mTraceEnabled)) {
-            trace(*info, LayerHistory::LayerVoteType::NoVote, 0);
+            it++;
+        } else {
+            if (CC_UNLIKELY(mTraceEnabled)) {
+                trace(*info, LayerHistory::LayerVoteType::NoVote, 0);
+            }
+            info->onLayerInactive(now);
+            // move this to the inactive map
+            mInactiveLayerInfos.insert({it->first, std::move(it->second)});
+            it = mActiveLayerInfos.erase(it);
         }
-
-        info->onLayerInactive(now);
-        std::swap(mLayerInfos[i], mLayerInfos[--mActiveLayersEnd]);
     }
 }
 
 void LayerHistory::clear() {
     std::lock_guard lock(mLock);
-
-    for (const auto& [layer, info] : activeLayers()) {
-        info->clearHistory(systemTime());
+    for (const auto& [key, value] : mActiveLayerInfos) {
+        value.second->clearHistory(systemTime());
     }
 }
 
 std::string LayerHistory::dump() const {
     std::lock_guard lock(mLock);
-    return base::StringPrintf("LayerHistory{size=%zu, active=%zu}", mLayerInfos.size(),
-                              mActiveLayersEnd);
+    return base::StringPrintf("LayerHistory{size=%zu, active=%zu}",
+                              mActiveLayerInfos.size() + mInactiveLayerInfos.size(),
+                              mActiveLayerInfos.size());
+}
+
+float LayerHistory::getLayerFramerate(nsecs_t now, int32_t id) const {
+    std::lock_guard lock(mLock);
+    auto [found, layerPair] = findLayer(id);
+    if (found != LayerHistory::layerStatus::NotFound) {
+        return layerPair->second->getFps(now).getValue();
+    }
+    return 0.f;
+}
+
+std::pair<LayerHistory::layerStatus, LayerHistory::LayerPair*> LayerHistory::findLayer(int32_t id) {
+    // the layer could be in either the active or inactive map, try both
+    auto it = mActiveLayerInfos.find(id);
+    if (it != mActiveLayerInfos.end()) {
+        return std::make_pair(LayerHistory::layerStatus::LayerInActiveMap, &(it->second));
+    }
+    it = mInactiveLayerInfos.find(id);
+    if (it != mInactiveLayerInfos.end()) {
+        return std::make_pair(LayerHistory::layerStatus::LayerInInactiveMap, &(it->second));
+    }
+    return std::make_pair(LayerHistory::layerStatus::NotFound, nullptr);
+}
+
+std::pair<LayerHistory::layerStatus, const LayerHistory::LayerPair*> LayerHistory::findLayer(
+        int32_t id) const {
+    // the layer could be in either the active or inactive map, try both
+    auto it = mActiveLayerInfos.find(id);
+    if (it != mActiveLayerInfos.end()) {
+        return std::make_pair(LayerHistory::layerStatus::LayerInActiveMap, &(it->second));
+    }
+    it = mInactiveLayerInfos.find(id);
+    if (it != mInactiveLayerInfos.end()) {
+        return std::make_pair(LayerHistory::layerStatus::LayerInInactiveMap, &(it->second));
+    }
+    return std::make_pair(LayerHistory::layerStatus::NotFound, nullptr);
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index 8d56951..cc55700 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -20,6 +20,7 @@
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
 
+#include <map>
 #include <memory>
 #include <mutex>
 #include <string>
@@ -72,34 +73,43 @@
     void deregisterLayer(Layer*);
     std::string dump() const;
 
+    // return the frames per second of the layer with the given sequence id.
+    float getLayerFramerate(nsecs_t now, int32_t id) const;
+
 private:
     friend class LayerHistoryTest;
     friend class TestableScheduler;
 
     using LayerPair = std::pair<Layer*, std::unique_ptr<LayerInfo>>;
-    using LayerInfos = std::vector<LayerPair>;
+    // keyed by id as returned from Layer::getSequence()
+    using LayerInfos = std::unordered_map<int32_t, LayerPair>;
 
-    struct ActiveLayers {
-        LayerInfos& infos;
-        const size_t index;
+    // Iterates over layers maps moving all active layers to mActiveLayerInfos and all inactive
+    // layers to mInactiveLayerInfos.
+    // worst case time complexity is O(2 * inactive + active)
+    void partitionLayers(nsecs_t now) REQUIRES(mLock);
 
-        auto begin() { return infos.begin(); }
-        auto end() { return begin() + static_cast<long>(index); }
+    enum class layerStatus {
+        NotFound,
+        LayerInActiveMap,
+        LayerInInactiveMap,
     };
 
-    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);
+    // looks up a layer by sequence id in both layerInfo maps.
+    // The first element indicates if and where the item was found
+    std::pair<layerStatus, LayerHistory::LayerPair*> findLayer(int32_t id) REQUIRES(mLock);
+    std::pair<layerStatus, const LayerHistory::LayerPair*> findLayer(int32_t id) const
+            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;
+    // Partitioned into two maps to facility two kinds of retrieval:
+    // 1. retrieval of a layer by id (attempt lookup in both maps)
+    // 2. retrieval of all active layers (iterate that map)
+    // The partitioning is allowed to become out of date but calling partitionLayers refreshes the
+    // validity of each map.
+    LayerInfos mActiveLayerInfos GUARDED_BY(mLock);
+    LayerInfos mInactiveLayerInfos GUARDED_BY(mLock);
 
     uint32_t mDisplayArea = 0;
 
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index ae61eeb..943615c 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -75,12 +75,16 @@
 }
 
 bool LayerInfo::isFrequent(nsecs_t now) const {
+    using fps_approx_ops::operator>=;
     // If we know nothing about this layer we consider it as frequent as it might be the start
     // of an animation.
     if (mFrameTimes.size() < kFrequentLayerWindowSize) {
         return true;
     }
+    return getFps(now) >= kMinFpsForFrequentLayer;
+}
 
+Fps LayerInfo::getFps(nsecs_t now) const {
     // Find the first active frame
     auto it = mFrameTimes.begin();
     for (; it != mFrameTimes.end(); ++it) {
@@ -91,14 +95,12 @@
 
     const auto numFrames = std::distance(it, mFrameTimes.end());
     if (numFrames < kFrequentLayerWindowSize) {
-        return false;
+        return Fps();
     }
 
-    using fps_approx_ops::operator>=;
-
     // Layer is considered frequent if the average frame rate is higher than the threshold
     const auto totalTime = mFrameTimes.back().queueTime - it->queueTime;
-    return Fps::fromPeriodNsecs(totalTime / (numFrames - 1)) >= kMinFpsForFrequentLayer;
+    return Fps::fromPeriodNsecs(totalTime / (numFrames - 1));
 }
 
 bool LayerInfo::isAnimating(nsecs_t now) const {
@@ -236,7 +238,7 @@
     if (!isFrequent(now)) {
         ALOGV("%s is infrequent", mName.c_str());
         mLastRefreshRate.animatingOrInfrequent = true;
-        // Infrequent layers vote for minimal refresh rate for
+        // Infrequent layers vote for mininal refresh rate for
         // battery saving purposes and also to prevent b/135718869.
         return {LayerHistory::LayerVoteType::Min, Fps()};
     }
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 690abda..2d88a4f 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -178,6 +178,9 @@
     // Returns a C string for tracing a vote
     const char* getTraceTag(LayerHistory::LayerVoteType type) const;
 
+    // Return the framerate of this layer.
+    Fps getFps(nsecs_t now) 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
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index e127ff7..818f1ed 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -233,6 +233,11 @@
         return mRefreshRateConfigs->getCurrentRefreshRate().getVsyncPeriod();
     }
 
+    // Returns the framerate of the layer with the given sequence ID
+    float getLayerFramerate(nsecs_t now, int32_t id) const {
+        return mLayerHistory.getLayerFramerate(now, id);
+    }
+
 private:
     friend class TestableScheduler;
 
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 9794639..d286a8c 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -1351,6 +1351,11 @@
     const sp<WindowInfosListenerInvoker> mWindowInfosListenerInvoker;
 
     std::unique_ptr<FlagManager> mFlagManager;
+
+    // returns the framerate of the layer with the given sequence ID
+    float getLayerFramerate(nsecs_t now, int32_t id) const {
+        return mScheduler->getLayerFramerate(now, id);
+    }
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 00687ad..cdb2240 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -63,33 +63,42 @@
     const LayerHistory& history() const { return mScheduler->mutableLayerHistory(); }
 
     LayerHistory::Summary summarizeLayerHistory(nsecs_t now) {
-        return history().summarize(*mScheduler->refreshRateConfigs(), now);
+        // LayerHistory::summarize makes no guarantee of the order of the elements in the summary
+        // however, for testing only, a stable order is required, therefore we sort the list here.
+        // Any tests requiring ordered results must create layers with names.
+        auto summary = history().summarize(*mScheduler->refreshRateConfigs(), now);
+        std::sort(summary.begin(), summary.end(),
+                  [](const RefreshRateConfigs::LayerRequirement& a,
+                     const RefreshRateConfigs::LayerRequirement& b) -> bool {
+                      return a.name < b.name;
+                  });
+        return summary;
     }
 
     size_t layerCount() const { return mScheduler->layerHistorySize(); }
-    size_t activeLayerCount() const NO_THREAD_SAFETY_ANALYSIS { return history().mActiveLayersEnd; }
+    size_t activeLayerCount() const NO_THREAD_SAFETY_ANALYSIS {
+        return history().mActiveLayerInfos.size();
+    }
 
     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); });
+        const auto& infos = history().mActiveLayerInfos;
+        return std::count_if(infos.begin(), infos.end(), [now](const auto& pair) {
+            return pair.second.second->isFrequent(now);
+        });
     }
 
     auto animatingLayerCount(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->isAnimating(now); });
+        const auto& infos = history().mActiveLayerInfos;
+        return std::count_if(infos.begin(), infos.end(), [now](const auto& pair) {
+            return pair.second.second->isAnimating(now);
+        });
     }
 
     void setDefaultLayerVote(Layer* layer,
                              LayerHistory::LayerVoteType vote) NO_THREAD_SAFETY_ANALYSIS {
-        for (auto& [layerUnsafe, info] : history().mLayerInfos) {
-            if (layerUnsafe == layer) {
-                info->setDefaultLayerVote(vote);
-                return;
-            }
+        auto [found, layerPair] = history().findLayer(layer->getSequence());
+        if (found != LayerHistory::layerStatus::NotFound) {
+            layerPair->second->setDefaultLayerVote(vote);
         }
     }
 
@@ -144,6 +153,8 @@
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
 
+    // history().registerLayer(layer, LayerHistory::LayerVoteType::Max);
+
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
 
@@ -368,9 +379,9 @@
 }
 
 TEST_F(LayerHistoryTest, multipleLayers) {
-    auto layer1 = createLayer();
-    auto layer2 = createLayer();
-    auto layer3 = createLayer();
+    auto layer1 = createLayer("A");
+    auto layer2 = createLayer("B");
+    auto layer3 = createLayer("C");
 
     EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer1, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
@@ -654,6 +665,29 @@
     EXPECT_EQ(1, animatingLayerCount(time));
 }
 
+TEST_F(LayerHistoryTest, getFramerate) {
+    auto layer = createLayer();
+
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // layer is active but infrequent.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+    }
+
+    float expectedFramerate = 1e9f / MAX_FREQUENT_LAYER_PERIOD_NS.count();
+    EXPECT_FLOAT_EQ(expectedFramerate, history().getLayerFramerate(time, layer->getSequence()));
+}
+
 TEST_F(LayerHistoryTest, heuristicLayer60Hz) {
     const auto layer = createLayer();
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index dabd2d2..364d8f1 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -67,11 +67,16 @@
 
     auto& mutableLayerHistory() { return mLayerHistory; }
 
-    size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS { return mLayerHistory.mLayerInfos.size(); }
-    size_t getNumActiveLayers() NO_THREAD_SAFETY_ANALYSIS { return mLayerHistory.mActiveLayersEnd; }
+    size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS {
+        return mLayerHistory.mActiveLayerInfos.size() + mLayerHistory.mInactiveLayerInfos.size();
+    }
 
     auto refreshRateConfigs() { return holdRefreshRateConfigs(); }
 
+    size_t getNumActiveLayers() NO_THREAD_SAFETY_ANALYSIS {
+        return mLayerHistory.mActiveLayerInfos.size();
+    }
+
     void replaceTouchTimer(int64_t millis) {
         if (mTouchTimer) {
             mTouchTimer.reset();