SF: Polish Fps class

For consistent comparison (which will later be changed to check ULP
distance instead of the magnitude-dependent threshold), express the
inequality operators in terms of equality, and avoid hashing floats.

Add Hz literals and (namespaced) operators.

Bug: 129481165
Bug: 185535769
Test: libsurfaceflinger_unittest
Test: dumpsys SurfaceFlinger --vsync
Change-Id: I79be5d2dd031218c4054774d2645efb337211538
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 8a45b66..314526a 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -41,7 +41,7 @@
       : mName(name),
         mOwnerUid(ownerUid),
         mDefaultVote(defaultVote),
-        mLayerVote({defaultVote, Fps(0.0f)}),
+        mLayerVote({defaultVote, Fps()}),
         mRefreshRateHistory(name) {}
 
 void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType,
@@ -93,10 +93,11 @@
         return false;
     }
 
+    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))
-            .greaterThanOrEqualWithMargin(kMinFpsForFrequentLayer);
+    return Fps::fromPeriodNsecs(totalTime / (numFrames - 1)) >= kMinFpsForFrequentLayer;
 }
 
 bool LayerInfo::isAnimating(nsecs_t now) const {
@@ -191,17 +192,17 @@
         return std::nullopt;
     }
 
-    const auto averageFrameTime = calculateAverageFrameTime();
-    if (averageFrameTime.has_value()) {
+    if (const auto averageFrameTime = calculateAverageFrameTime()) {
         const auto refreshRate = Fps::fromPeriodNsecs(*averageFrameTime);
         const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);
         if (refreshRateConsistent) {
             const auto knownRefreshRate = refreshRateConfigs.findClosestKnownFrameRate(refreshRate);
-            // To avoid oscillation, use the last calculated refresh rate if it is
-            // close enough
+            using fps_approx_ops::operator!=;
+
+            // To avoid oscillation, use the last calculated refresh rate if it is close enough.
             if (std::abs(mLastRefreshRate.calculated.getValue() - refreshRate.getValue()) >
                         MARGIN &&
-                !mLastRefreshRate.reported.equalsWithMargin(knownRefreshRate)) {
+                mLastRefreshRate.reported != knownRefreshRate) {
                 mLastRefreshRate.calculated = refreshRate;
                 mLastRefreshRate.reported = knownRefreshRate;
             }
@@ -228,15 +229,15 @@
     if (isAnimating(now)) {
         ALOGV("%s is animating", mName.c_str());
         mLastRefreshRate.animatingOrInfrequent = true;
-        return {LayerHistory::LayerVoteType::Max, Fps(0.0f)};
+        return {LayerHistory::LayerVoteType::Max, Fps()};
     }
 
     if (!isFrequent(now)) {
         ALOGV("%s is infrequent", mName.c_str());
         mLastRefreshRate.animatingOrInfrequent = true;
-        // Infrequent layers vote for mininal refresh rate for
+        // Infrequent layers vote for minimal refresh rate for
         // battery saving purposes and also to prevent b/135718869.
-        return {LayerHistory::LayerVoteType::Min, Fps(0.0f)};
+        return {LayerHistory::LayerVoteType::Min, Fps()};
     }
 
     // If the layer was previously tagged as animating or infrequent, we clear
@@ -253,7 +254,7 @@
     }
 
     ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());
-    return {LayerHistory::LayerVoteType::Max, Fps(0.0f)};
+    return {LayerHistory::LayerVoteType::Max, Fps()};
 }
 
 const char* LayerInfo::getTraceTag(android::scheduler::LayerHistory::LayerVoteType type) const {
@@ -300,9 +301,13 @@
 bool LayerInfo::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 =
+    const auto [min, max] =
+            std::minmax_element(mRefreshRates.begin(), mRefreshRates.end(),
+                                [](const auto& lhs, const auto& rhs) {
+                                    return isStrictlyLess(lhs.refreshRate, rhs.refreshRate);
+                                });
+
+    const bool consistent =
             max->refreshRate.getValue() - min->refreshRate.getValue() < MARGIN_CONSISTENT_FPS;
 
     if (CC_UNLIKELY(sTraceEnabled)) {
@@ -321,4 +326,4 @@
 } // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file
+#pragma clang diagnostic pop // ignored "-Wextra"
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index ce9783c..92abbae 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -51,7 +51,7 @@
     // 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 kFrequentLayerWindowSize = 3;
-    static constexpr Fps kMinFpsForFrequentLayer{10.0f};
+    static constexpr Fps kMinFpsForFrequentLayer = 10_Hz;
     static constexpr auto kMaxPeriodForFrequentLayerNs =
             std::chrono::nanoseconds(kMinFpsForFrequentLayer.getPeriodNsecs()) + 1ms;
 
@@ -62,7 +62,7 @@
     // Holds information about the layer vote
     struct LayerVote {
         LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
-        Fps fps{0.0f};
+        Fps fps;
         Seamlessness seamlessness = Seamlessness::Default;
     };
 
@@ -86,19 +86,17 @@
         using Seamlessness = scheduler::Seamlessness;
 
         Fps rate;
-        FrameRateCompatibility type;
-        Seamlessness seamlessness;
+        FrameRateCompatibility type = FrameRateCompatibility::Default;
+        Seamlessness seamlessness = Seamlessness::Default;
 
-        FrameRate()
-              : rate(0),
-                type(FrameRateCompatibility::Default),
-                seamlessness(Seamlessness::Default) {}
+        FrameRate() = default;
+
         FrameRate(Fps rate, FrameRateCompatibility type,
                   Seamlessness seamlessness = Seamlessness::OnlySeamless)
               : rate(rate), type(type), seamlessness(getSeamlessness(rate, seamlessness)) {}
 
         bool operator==(const FrameRate& other) const {
-            return rate.equalsWithMargin(other.rate) && type == other.type &&
+            return isApproxEqual(rate, other.rate) && type == other.type &&
                     seamlessness == other.seamlessness;
         }
 
@@ -151,7 +149,7 @@
     void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; }
 
     // Resets the layer vote to its default.
-    void resetLayerVote() { mLayerVote = {mDefaultVote, Fps(0.0f), Seamlessness::Default}; }
+    void resetLayerVote() { mLayerVote = {mDefaultVote, Fps(), Seamlessness::Default}; }
 
     std::string getName() const { return mName; }
 
@@ -201,9 +199,9 @@
     // Holds information about the calculated and reported refresh rate
     struct RefreshRateHeuristicData {
         // Rate calculated on the layer
-        Fps calculated{0.0f};
+        Fps calculated;
         // Last reported rate for LayerInfo::getRefreshRate()
-        Fps reported{0.0f};
+        Fps reported;
         // Whether the last reported rate for LayerInfo::getRefreshRate()
         // was due to animation or infrequent updates
         bool animatingOrInfrequent = false;
@@ -229,14 +227,8 @@
 
         // Holds the refresh rate when it was calculated
         struct RefreshRateData {
-            Fps refreshRate{0.0f};
+            Fps refreshRate;
             nsecs_t timestamp = 0;
-
-            bool operator<(const RefreshRateData& other) const {
-                // We don't need comparison with margins since we are using
-                // this to find the min and max refresh rates.
-                return refreshRate.getValue() < other.refreshRate.getValue();
-            }
         };
 
         // Holds tracing strings
@@ -268,7 +260,7 @@
 
     // Used for sanitizing the heuristic data. If two frames are less than
     // this period apart from each other they'll be considered as duplicates.
-    static constexpr nsecs_t kMinPeriodBetweenFrames = Fps(240.f).getPeriodNsecs();
+    static constexpr nsecs_t kMinPeriodBetweenFrames = (240_Hz).getPeriodNsecs();
     // Used for sanitizing the heuristic data. If two frames are more than
     // this period apart from each other, the interval between them won't be
     // taken into account when calculating average frame rate.
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index c9f00a0..aabd88a 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -42,19 +42,18 @@
 }
 
 std::vector<Fps> constructKnownFrameRates(const DisplayModes& modes) {
-    std::vector<Fps> knownFrameRates = {Fps(24.0f), Fps(30.0f), Fps(45.0f), Fps(60.0f), Fps(72.0f)};
+    std::vector<Fps> knownFrameRates = {24_Hz, 30_Hz, 45_Hz, 60_Hz, 72_Hz};
     knownFrameRates.reserve(knownFrameRates.size() + modes.size());
 
-    // Add all supported refresh rates to the set
+    // Add all supported refresh rates.
     for (const auto& mode : modes) {
-        const auto refreshRate = Fps::fromPeriodNsecs(mode->getVsyncPeriod());
-        knownFrameRates.emplace_back(refreshRate);
+        knownFrameRates.push_back(Fps::fromPeriodNsecs(mode->getVsyncPeriod()));
     }
 
-    // Sort and remove duplicates
-    std::sort(knownFrameRates.begin(), knownFrameRates.end(), Fps::comparesLess);
+    // Sort and remove duplicates.
+    std::sort(knownFrameRates.begin(), knownFrameRates.end(), isStrictlyLess);
     knownFrameRates.erase(std::unique(knownFrameRates.begin(), knownFrameRates.end(),
-                                      Fps::EqualsWithMargin()),
+                                      isApproxEqual),
                           knownFrameRates.end());
     return knownFrameRates;
 }
@@ -64,6 +63,11 @@
 using AllRefreshRatesMapType = RefreshRateConfigs::AllRefreshRatesMapType;
 using RefreshRate = RefreshRateConfigs::RefreshRate;
 
+bool RefreshRate::inPolicy(Fps minRefreshRate, Fps maxRefreshRate) const {
+    using fps_approx_ops::operator<=;
+    return minRefreshRate <= getFps() && getFps() <= maxRefreshRate;
+}
+
 std::string RefreshRate::toString() const {
     return base::StringPrintf("{id=%d, hwcId=%d, fps=%.2f, width=%d, height=%d group=%d}",
                               getModeId().value(), mode->getHwcId(), getFps().getValue(),
@@ -110,14 +114,14 @@
 
 bool RefreshRateConfigs::isVoteAllowed(const LayerRequirement& layer,
                                        const RefreshRate& refreshRate) const {
+    using namespace fps_approx_ops;
+
     switch (layer.vote) {
         case LayerVoteType::ExplicitExactOrMultiple:
         case LayerVoteType::Heuristic:
             if (mConfig.frameRateMultipleThreshold != 0 &&
-                refreshRate.getFps().greaterThanOrEqualWithMargin(
-                        Fps(mConfig.frameRateMultipleThreshold)) &&
-                layer.desiredRefreshRate.lessThanWithMargin(
-                        Fps(mConfig.frameRateMultipleThreshold / 2))) {
+                refreshRate.getFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold) &&
+                layer.desiredRefreshRate < Fps::fromValue(mConfig.frameRateMultipleThreshold / 2)) {
                 // Don't vote high refresh rates past the threshold for layers with a low desired
                 // refresh rate. For example, desired 24 fps with 120 Hz threshold means no vote for
                 // 120 Hz, but desired 60 fps should have a vote.
@@ -247,7 +251,7 @@
 };
 
 RefreshRate RefreshRateConfigs::getBestRefreshRate(const std::vector<LayerRequirement>& layers,
-                                                   const GlobalSignals& globalSignals,
+                                                   GlobalSignals globalSignals,
                                                    GlobalSignals* outSignalsConsidered) const {
     std::lock_guard lock(mLock);
 
@@ -269,7 +273,7 @@
 }
 
 std::optional<RefreshRate> RefreshRateConfigs::getCachedBestRefreshRate(
-        const std::vector<LayerRequirement>& layers, const GlobalSignals& globalSignals,
+        const std::vector<LayerRequirement>& layers, GlobalSignals globalSignals,
         GlobalSignals* outSignalsConsidered) const {
     const bool sameAsLastCall = lastBestRefreshRateInvocation &&
             lastBestRefreshRateInvocation->layerRequirements == layers &&
@@ -286,7 +290,7 @@
 }
 
 RefreshRate RefreshRateConfigs::getBestRefreshRateLocked(
-        const std::vector<LayerRequirement>& layers, const GlobalSignals& globalSignals,
+        const std::vector<LayerRequirement>& layers, GlobalSignals globalSignals,
         GlobalSignals* outSignalsConsidered) const {
     ATRACE_CALL();
     ALOGV("getBestRefreshRate %zu layers", layers.size());
@@ -370,7 +374,7 @@
     // move out the of range if layers explicitly request a different refresh
     // rate.
     const bool primaryRangeIsSingleRate =
-            policy->primaryRange.min.equalsWithMargin(policy->primaryRange.max);
+            isApproxEqual(policy->primaryRange.min, policy->primaryRange.max);
 
     if (!globalSignals.touch && globalSignals.idle &&
         !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
@@ -498,8 +502,11 @@
             return explicitExact == 0;
         }
     }();
+
+    using fps_approx_ops::operator<;
+
     if (globalSignals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
-        bestRefreshRate->getFps().lessThanWithMargin(touchRefreshRate.getFps())) {
+        bestRefreshRate->getFps() < touchRefreshRate.getFps()) {
         setTouchConsidered();
         ALOGV("TouchBoost - choose %s", touchRefreshRate.getName().c_str());
         return touchRefreshRate;
@@ -552,7 +559,8 @@
 }
 
 RefreshRateConfigs::UidToFrameRateOverride RefreshRateConfigs::getFrameRateOverrides(
-        const std::vector<LayerRequirement>& layers, Fps displayFrameRate, bool touch) const {
+        const std::vector<LayerRequirement>& layers, Fps displayFrameRate,
+        GlobalSignals globalSignals) const {
     ATRACE_CALL();
     if (!mSupportsFrameRateOverride) return {};
 
@@ -570,7 +578,7 @@
                                 return layer->vote == LayerVoteType::ExplicitExactOrMultiple;
                             });
 
-        if (touch && hasExplicitExactOrMultiple) {
+        if (globalSignals.touch && hasExplicitExactOrMultiple) {
             continue;
         }
 
@@ -798,8 +806,10 @@
         ALOGE("Default mode is not in the primary range.");
         return false;
     }
-    return policy.appRequestRange.min.lessThanOrEqualWithMargin(policy.primaryRange.min) &&
-            policy.appRequestRange.max.greaterThanOrEqualWithMargin(policy.primaryRange.max);
+
+    using namespace fps_approx_ops;
+    return policy.appRequestRange.min <= policy.primaryRange.min &&
+            policy.appRequestRange.max >= policy.primaryRange.max;
 }
 
 status_t RefreshRateConfigs::setDisplayManagerPolicy(const Policy& policy) {
@@ -925,19 +935,21 @@
 }
 
 Fps RefreshRateConfigs::findClosestKnownFrameRate(Fps frameRate) const {
-    if (frameRate.lessThanOrEqualWithMargin(*mKnownFrameRates.begin())) {
-        return *mKnownFrameRates.begin();
+    using namespace fps_approx_ops;
+
+    if (frameRate <= mKnownFrameRates.front()) {
+        return mKnownFrameRates.front();
     }
 
-    if (frameRate.greaterThanOrEqualWithMargin(*std::prev(mKnownFrameRates.end()))) {
-        return *std::prev(mKnownFrameRates.end());
+    if (frameRate >= mKnownFrameRates.back()) {
+        return mKnownFrameRates.back();
     }
 
     auto lowerBound = std::lower_bound(mKnownFrameRates.begin(), mKnownFrameRates.end(), frameRate,
-                                       Fps::comparesLess);
+                                       isStrictlyLess);
 
-    const auto distance1 = std::abs((frameRate.getValue() - lowerBound->getValue()));
-    const auto distance2 = std::abs((frameRate.getValue() - std::prev(lowerBound)->getValue()));
+    const auto distance1 = std::abs(frameRate.getValue() - lowerBound->getValue());
+    const auto distance2 = std::abs(frameRate.getValue() - std::prev(lowerBound)->getValue());
     return distance1 < distance2 ? *lowerBound : *std::prev(lowerBound);
 }
 
@@ -956,7 +968,7 @@
     }
     if (minByPolicy == maxByPolicy) {
         // when min primary range in display manager policy is below device min turn on the timer.
-        if (currentPolicy->primaryRange.min.lessThanWithMargin(deviceMin.getFps())) {
+        if (isApproxLess(currentPolicy->primaryRange.min, deviceMin.getFps())) {
             return RefreshRateConfigs::KernelIdleTimerAction::TurnOn;
         }
         return RefreshRateConfigs::KernelIdleTimerAction::TurnOff;
@@ -982,14 +994,14 @@
 }
 
 bool RefreshRateConfigs::isFractionalPairOrMultiple(Fps smaller, Fps bigger) {
-    if (smaller.getValue() > bigger.getValue()) {
+    if (isStrictlyLess(bigger, smaller)) {
         return isFractionalPairOrMultiple(bigger, smaller);
     }
 
     const auto multiplier = std::round(bigger.getValue() / smaller.getValue());
     constexpr float kCoef = 1000.f / 1001.f;
-    return bigger.equalsWithMargin(Fps(smaller.getValue() * multiplier / kCoef)) ||
-            bigger.equalsWithMargin(Fps(smaller.getValue() * multiplier * kCoef));
+    return isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier / kCoef)) ||
+            isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier * kCoef));
 }
 
 void RefreshRateConfigs::dump(std::string& result) const {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 3abf83d..53472ef 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -76,19 +76,15 @@
 
         // Checks whether the fps of this RefreshRate struct is within a given min and max refresh
         // rate passed in. Margin of error is applied to the boundaries for approximation.
-        bool inPolicy(Fps minRefreshRate, Fps maxRefreshRate) const {
-            return minRefreshRate.lessThanOrEqualWithMargin(getFps()) &&
-                    getFps().lessThanOrEqualWithMargin(maxRefreshRate);
-        }
+        bool inPolicy(Fps minRefreshRate, Fps maxRefreshRate) const;
 
-        bool operator!=(const RefreshRate& other) const { return mode != other.mode; }
+        bool operator==(const RefreshRate& other) const { return mode == other.mode; }
+        bool operator!=(const RefreshRate& other) const { return !operator==(other); }
 
         bool operator<(const RefreshRate& other) const {
-            return getFps().getValue() < other.getFps().getValue();
+            return isStrictlyLess(getFps(), other.getFps());
         }
 
-        bool operator==(const RefreshRate& other) const { return !(*this != other); }
-
         std::string toString() const;
         friend std::ostream& operator<<(std::ostream& os, const RefreshRate& refreshRate) {
             return os << refreshRate.toString();
@@ -105,11 +101,11 @@
             std::unordered_map<DisplayModeId, std::unique_ptr<const RefreshRate>>;
 
     struct FpsRange {
-        Fps min{0.0f};
-        Fps max{std::numeric_limits<float>::max()};
+        Fps min = Fps::fromValue(0.f);
+        Fps max = Fps::fromValue(std::numeric_limits<float>::max());
 
         bool operator==(const FpsRange& other) const {
-            return min.equalsWithMargin(other.min) && max.equalsWithMargin(other.max);
+            return isApproxEqual(min, other.min) && isApproxEqual(max, other.max);
         }
 
         bool operator!=(const FpsRange& other) const { return !(*this == other); }
@@ -221,7 +217,7 @@
         // Layer vote type.
         LayerVoteType vote = LayerVoteType::NoVote;
         // Layer's desired refresh rate, if applicable.
-        Fps desiredRefreshRate{0.0f};
+        Fps desiredRefreshRate;
         // If a seamless mode switch is required.
         Seamlessness seamlessness = Seamlessness::Default;
         // Layer's weight in the range of [0, 1]. The higher the weight the more impact this layer
@@ -232,7 +228,7 @@
 
         bool operator==(const LayerRequirement& other) const {
             return name == other.name && vote == other.vote &&
-                    desiredRefreshRate.equalsWithMargin(other.desiredRefreshRate) &&
+                    isApproxEqual(desiredRefreshRate, other.desiredRefreshRate) &&
                     seamlessness == other.seamlessness && weight == other.weight &&
                     focused == other.focused;
         }
@@ -247,18 +243,14 @@
         // True if the system hasn't seen any buffers posted to layers recently.
         bool idle = false;
 
-        bool operator==(const GlobalSignals& other) const {
+        bool operator==(GlobalSignals other) const {
             return touch == other.touch && idle == other.idle;
         }
     };
 
-    // Returns the refresh rate that fits best to the given layers.
-    //   layers - The layer requirements to consider.
-    //   globalSignals - global state of touch and idle
-    //   outSignalsConsidered - An output param that tells the caller whether the refresh rate was
-    //                          chosen based on touch boost and/or idle timer.
-    RefreshRate getBestRefreshRate(const std::vector<LayerRequirement>& layers,
-                                   const GlobalSignals& globalSignals,
+    // Returns the refresh rate that best fits the given layers. outSignalsConsidered returns
+    // whether the refresh rate was chosen based on touch boost and/or idle timer.
+    RefreshRate getBestRefreshRate(const std::vector<LayerRequirement>&, GlobalSignals,
                                    GlobalSignals* outSignalsConsidered = nullptr) const
             EXCLUDES(mLock);
 
@@ -349,13 +341,10 @@
     static bool isFractionalPairOrMultiple(Fps, Fps);
 
     using UidToFrameRateOverride = std::map<uid_t, Fps>;
+
     // Returns the frame rate override for each uid.
-    //
-    // @param layers list of visible layers
-    // @param displayFrameRate the display frame rate
-    // @param touch whether touch timer is active (i.e. user touched the screen recently)
-    UidToFrameRateOverride getFrameRateOverrides(const std::vector<LayerRequirement>& layers,
-                                                 Fps displayFrameRate, bool touch) const
+    UidToFrameRateOverride getFrameRateOverrides(const std::vector<LayerRequirement>&,
+                                                 Fps displayFrameRate, GlobalSignals) const
             EXCLUDES(mLock);
 
     bool supportsKernelIdleTimer() const { return mConfig.supportKernelIdleTimer; }
@@ -396,13 +385,12 @@
             const std::function<bool(const RefreshRate&)>& shouldAddRefreshRate,
             std::vector<const RefreshRate*>* outRefreshRates) REQUIRES(mLock);
 
-    std::optional<RefreshRate> getCachedBestRefreshRate(const std::vector<LayerRequirement>& layers,
-                                                        const GlobalSignals& globalSignals,
+    std::optional<RefreshRate> getCachedBestRefreshRate(const std::vector<LayerRequirement>&,
+                                                        GlobalSignals,
                                                         GlobalSignals* outSignalsConsidered) const
             REQUIRES(mLock);
 
-    RefreshRate getBestRefreshRateLocked(const std::vector<LayerRequirement>& layers,
-                                         const GlobalSignals& globalSignals,
+    RefreshRate getBestRefreshRateLocked(const std::vector<LayerRequirement>&, GlobalSignals,
                                          GlobalSignals* outSignalsConsidered) const REQUIRES(mLock);
 
     // Returns the refresh rate with the highest score in the collection specified from begin
diff --git a/services/surfaceflinger/Scheduler/RefreshRateStats.h b/services/surfaceflinger/Scheduler/RefreshRateStats.h
index 208a767..80aa96f 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateStats.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateStats.h
@@ -16,15 +16,17 @@
 
 #pragma once
 
+#include <chrono>
 #include <numeric>
 
+#include <android-base/stringprintf.h>
+#include <ftl/small_map.h>
+#include <utils/Timers.h>
+
 #include "Fps.h"
 #include "Scheduler/SchedulerUtils.h"
 #include "TimeStats/TimeStats.h"
 
-#include "android-base/stringprintf.h"
-#include "utils/Timers.h"
-
 namespace android::scheduler {
 
 /**
@@ -40,6 +42,7 @@
     static constexpr int64_t MS_PER_DAY = 24 * MS_PER_HOUR;
 
 public:
+    // TODO(b/185535769): Inject clock to avoid sleeping in tests.
     RefreshRateStats(TimeStats& timeStats, Fps currentRefreshRate,
                      android::hardware::graphics::composer::hal::PowerMode currentPowerMode)
           : mTimeStats(timeStats),
@@ -58,7 +61,7 @@
     // Sets config mode. If the mode has changed, it records how much time was spent in the previous
     // mode.
     void setRefreshRate(Fps currRefreshRate) {
-        if (mCurrentRefreshRate.equalsWithMargin(currRefreshRate)) {
+        if (isApproxEqual(mCurrentRefreshRate, currRefreshRate)) {
             return;
         }
         mTimeStats.incrementRefreshRateSwitches();
@@ -66,25 +69,26 @@
         mCurrentRefreshRate = currRefreshRate;
     }
 
-    // Returns a map between human readable refresh rate and number of seconds the device spent in
-    // that mode.
-    std::unordered_map<std::string, int64_t> getTotalTimes() {
+    // Maps stringified refresh rate to total time spent in that mode.
+    using TotalTimes = ftl::SmallMap<std::string, std::chrono::milliseconds, 3>;
+
+    TotalTimes getTotalTimes() {
         // If the power mode is on, then we are probably switching between the config modes. If
         // it's not then the screen is probably off. Make sure to flush times before printing
         // them.
         flushTime();
 
-        std::unordered_map<std::string, int64_t> totalTime;
-        // Multiple configs may map to the same name, e.g. "60fps". Add the
-        // times for such configs together.
-        for (const auto& [configId, time] : mConfigModesTotalTime) {
-            totalTime[to_string(configId)] = 0;
+        TotalTimes totalTimes = ftl::init::map("ScreenOff", mScreenOffTime);
+        const auto zero = std::chrono::milliseconds::zero();
+
+        // Sum the times for modes that map to the same name, e.g. "60 Hz".
+        for (const auto& [fps, time] : mFpsTotalTimes) {
+            const auto string = to_string(fps);
+            const auto total = std::as_const(totalTimes).get(string).value_or(std::cref(zero));
+            totalTimes.emplace_or_replace(string, total.get() + time);
         }
-        for (const auto& [configId, time] : mConfigModesTotalTime) {
-            totalTime[to_string(configId)] += time;
-        }
-        totalTime["ScreenOff"] = mScreenOffTime;
-        return totalTime;
+
+        return totalTimes;
     }
 
     // Traverses through the map of config modes and returns how long they've been running in easy
@@ -102,28 +106,32 @@
     // Calculates the time that passed in ms between the last time we recorded time and the time
     // this method was called.
     void flushTime() {
-        nsecs_t currentTime = systemTime();
-        nsecs_t timeElapsed = currentTime - mPreviousRecordedTime;
-        int64_t timeElapsedMs = ns2ms(timeElapsed);
+        const nsecs_t currentTime = systemTime();
+        const nsecs_t timeElapsed = currentTime - mPreviousRecordedTime;
         mPreviousRecordedTime = currentTime;
 
+        const auto duration = std::chrono::milliseconds{ns2ms(timeElapsed)};
+        const auto zero = std::chrono::milliseconds::zero();
+
         uint32_t fps = 0;
+
         if (mCurrentPowerMode == android::hardware::graphics::composer::hal::PowerMode::ON) {
             // Normal power mode is counted under different config modes.
-            if (mConfigModesTotalTime.find(mCurrentRefreshRate) == mConfigModesTotalTime.end()) {
-                mConfigModesTotalTime[mCurrentRefreshRate] = 0;
-            }
-            mConfigModesTotalTime[mCurrentRefreshRate] += timeElapsedMs;
+            const auto total = std::as_const(mFpsTotalTimes)
+                                       .get(mCurrentRefreshRate)
+                                       .value_or(std::cref(zero));
+            mFpsTotalTimes.emplace_or_replace(mCurrentRefreshRate, total.get() + duration);
+
             fps = static_cast<uint32_t>(mCurrentRefreshRate.getIntValue());
         } else {
-            mScreenOffTime += timeElapsedMs;
+            mScreenOffTime += duration;
         }
         mTimeStats.recordRefreshRate(fps, timeElapsed);
     }
 
     // Formats the time in milliseconds into easy to read format.
-    static std::string getDateFormatFromMs(int64_t timeMs) {
-        auto [days, dayRemainderMs] = std::div(timeMs, MS_PER_DAY);
+    static std::string getDateFormatFromMs(std::chrono::milliseconds time) {
+        auto [days, dayRemainderMs] = std::div(static_cast<int64_t>(time.count()), MS_PER_DAY);
         auto [hours, hourRemainderMs] = std::div(dayRemainderMs, MS_PER_HOUR);
         auto [mins, minsRemainderMs] = std::div(hourRemainderMs, MS_PER_MIN);
         auto [sec, secRemainderMs] = std::div(minsRemainderMs, MS_PER_S);
@@ -138,9 +146,8 @@
     Fps mCurrentRefreshRate;
     android::hardware::graphics::composer::hal::PowerMode mCurrentPowerMode;
 
-    std::unordered_map<Fps, int64_t /* duration in ms */, std::hash<Fps>, Fps::EqualsInBuckets>
-            mConfigModesTotalTime;
-    int64_t mScreenOffTime = 0;
+    ftl::SmallMap<Fps, std::chrono::milliseconds, 2, FpsApproxEqual> mFpsTotalTimes;
+    std::chrono::milliseconds mScreenOffTime = std::chrono::milliseconds::zero();
 
     nsecs_t mPreviousRecordedTime = systemTime();
 };
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index c6a19de..47b3568 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -206,14 +206,14 @@
     {
         const auto iter = mFrameRateOverridesFromBackdoor.find(uid);
         if (iter != mFrameRateOverridesFromBackdoor.end()) {
-            return std::make_optional<Fps>(iter->second);
+            return iter->second;
         }
     }
 
     {
         const auto iter = mFrameRateOverridesByContent.find(uid);
         if (iter != mFrameRateOverridesByContent.end()) {
-            return std::make_optional<Fps>(iter->second);
+            return iter->second;
         }
     }
 
@@ -697,15 +697,16 @@
         return mRefreshRateConfigs->getCurrentRefreshRate();
     }();
 
-    constexpr Fps FPS_THRESHOLD_FOR_KERNEL_TIMER{65.0f};
-    if (state == TimerState::Reset &&
-        refreshRate.getFps().greaterThanWithMargin(FPS_THRESHOLD_FOR_KERNEL_TIMER)) {
+    constexpr Fps FPS_THRESHOLD_FOR_KERNEL_TIMER = 65_Hz;
+    using namespace fps_approx_ops;
+
+    if (state == TimerState::Reset && refreshRate.getFps() > FPS_THRESHOLD_FOR_KERNEL_TIMER) {
         // If we're not in performance mode then the kernel timer shouldn't do
         // anything, as the refresh rate during DPU power collapse will be the
         // same.
         resyncToHardwareVsync(true /* makeAvailable */, refreshRate.getVsyncPeriod());
     } else if (state == TimerState::Expired &&
-               refreshRate.getFps().lessThanOrEqualWithMargin(FPS_THRESHOLD_FOR_KERNEL_TIMER)) {
+               refreshRate.getFps() <= FPS_THRESHOLD_FOR_KERNEL_TIMER) {
         // Disable HW VSYNC if the timer expired, as we don't need it enabled if
         // we're not pushing frames, and if we're in PERFORMANCE mode then we'll
         // need to update the VsyncController model anyway.
@@ -781,13 +782,12 @@
     if (!consideredSignals.idle) {
         const auto frameRateOverrides =
                 refreshRateConfigs->getFrameRateOverrides(mFeatures.contentRequirements,
-                                                          displayRefreshRate,
-                                                          consideredSignals.touch);
+                                                          displayRefreshRate, consideredSignals);
         std::lock_guard lock(mFrameRateOverridesLock);
         if (!std::equal(mFrameRateOverridesByContent.begin(), mFrameRateOverridesByContent.end(),
                         frameRateOverrides.begin(), frameRateOverrides.end(),
-                        [](const std::pair<uid_t, Fps>& a, const std::pair<uid_t, Fps>& b) {
-                            return a.first == b.first && a.second.equalsWithMargin(b.second);
+                        [](const auto& lhs, const auto& rhs) {
+                            return lhs.first == rhs.first && isApproxEqual(lhs.second, rhs.second);
                         })) {
             mFrameRateOverridesByContent = frameRateOverrides;
             return true;
@@ -912,7 +912,8 @@
 
     std::lock_guard lock(mFrameRateOverridesLock);
     if (frameRateOverride.frameRateHz != 0.f) {
-        mFrameRateOverridesFromBackdoor[frameRateOverride.uid] = Fps(frameRateOverride.frameRateHz);
+        mFrameRateOverridesFromBackdoor[frameRateOverride.uid] =
+                Fps::fromValue(frameRateOverride.frameRateHz);
     } else {
         mFrameRateOverridesFromBackdoor.erase(frameRateOverride.uid);
     }
diff --git a/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp b/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp
index 43e0297..ff31651 100644
--- a/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp
@@ -48,14 +48,12 @@
 }
 
 PhaseOffsets::VsyncConfigSet VsyncConfiguration::getConfigsForRefreshRateLocked(Fps fps) const {
-    const auto iter = mOffsetsCache.find(fps);
-    if (iter != mOffsetsCache.end()) {
-        return iter->second;
+    if (const auto offsets = mOffsetsCache.get(fps)) {
+        return offsets->get();
     }
 
-    const auto offset = constructOffsets(fps.getPeriodNsecs());
-    mOffsetsCache[fps] = offset;
-    return offset;
+    const auto [it, _] = mOffsetsCache.try_emplace(fps, constructOffsets(fps.getPeriodNsecs()));
+    return it->second;
 }
 
 void VsyncConfiguration::dump(std::string& result) const {
diff --git a/services/surfaceflinger/Scheduler/VsyncConfiguration.h b/services/surfaceflinger/Scheduler/VsyncConfiguration.h
index 3e53b3f..8447512 100644
--- a/services/surfaceflinger/Scheduler/VsyncConfiguration.h
+++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.h
@@ -17,10 +17,10 @@
 #pragma once
 
 #include <mutex>
-#include <type_traits>
-#include <unordered_map>
-#include <vector>
+#include <optional>
+#include <string>
 
+#include <ftl/small_map.h>
 #include <utils/Timers.h>
 
 #include "Fps.h"
@@ -88,9 +88,8 @@
 
     VsyncConfigSet getConfigsForRefreshRateLocked(Fps fps) const REQUIRES(mLock);
 
-    mutable std::unordered_map<Fps, VsyncConfigSet, std::hash<Fps>, Fps::EqualsInBuckets>
-            mOffsetsCache GUARDED_BY(mLock);
-    std::atomic<Fps> mRefreshRateFps GUARDED_BY(mLock);
+    mutable ftl::SmallMap<Fps, VsyncConfigSet, 2, FpsApproxEqual> mOffsetsCache GUARDED_BY(mLock);
+    Fps mRefreshRateFps GUARDED_BY(mLock);
     mutable std::mutex mLock;
 };