Add Fps class

Add a class to wrap a fps value. This is useful because across
the code we
 - convert between vsyncPeriod and fps
 - compare with tolerance (this ensures we use consistent tolerance)
 - consistent toString method

Bug: 159590486
Test: presubmit
Change-Id: Iebb77a33a2f822056642aa61bd6fac6514aa656d
diff --git a/services/surfaceflinger/Fps.h b/services/surfaceflinger/Fps.h
new file mode 100644
index 0000000..38a9af0
--- /dev/null
+++ b/services/surfaceflinger/Fps.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cmath>
+#include <ostream>
+#include <string>
+
+#include <android-base/stringprintf.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+// Value which represents "frames per second". This class is a wrapper around
+// float, providing some useful utilities, such as comparisons with tolerance
+// and converting between period duruation and frequency.
+class Fps {
+public:
+    static constexpr Fps fromPeriodNsecs(nsecs_t period) { return Fps(1e9f / period, period); }
+
+    Fps() = default;
+    explicit constexpr Fps(float fps)
+          : fps(fps), period(fps == 0.0f ? 0 : static_cast<nsecs_t>(1e9f / fps)) {}
+
+    constexpr float getValue() const { return fps; }
+
+    constexpr nsecs_t getPeriodNsecs() const { return period; }
+
+    bool equalsWithMargin(const Fps& other) const { return std::abs(fps - other.fps) < kMargin; }
+
+    // DO NOT use for std::sort. Instead use comparesLess().
+    bool lessThanWithMargin(const Fps& other) const { return fps + kMargin < other.fps; }
+
+    bool greaterThanWithMargin(const Fps& other) const { return fps > other.fps + kMargin; }
+
+    bool lessThanOrEqualWithMargin(const Fps& other) const { return !greaterThanWithMargin(other); }
+
+    bool greaterThanOrEqualWithMargin(const Fps& other) const { return !lessThanWithMargin(other); }
+
+    bool isValid() const { return fps > 0.0f; }
+
+    int getIntValue() const { return static_cast<int>(std::round(fps)); }
+
+    // Use this comparator for sorting. Using a comparator with margins can
+    // cause std::sort to crash.
+    inline static bool comparesLess(const Fps& left, const Fps& right) {
+        return left.fps < right.fps;
+    }
+
+    // Compares two FPS with margin.
+    // Transitivity is not guaranteed, i.e. a==b and b==c doesn't imply a==c.
+    // DO NOT use with hash maps. Instead use EqualsInBuckets.
+    struct EqualsWithMargin {
+        bool operator()(const Fps& left, const Fps& right) const {
+            return left.equalsWithMargin(right);
+        }
+    };
+
+    // Equals comparator which can be used with hash maps.
+    // It's guaranteed that if two elements are equal, then their hashes are equal.
+    struct EqualsInBuckets {
+        bool operator()(const Fps& left, const Fps& right) const {
+            return left.getBucket() == right.getBucket();
+        }
+    };
+
+    inline friend std::string to_string(const Fps& fps) {
+        return base::StringPrintf("%.2ffps", fps.fps);
+    }
+
+    inline friend std::ostream& operator<<(std::ostream& os, const Fps& fps) {
+        return os << to_string(fps);
+    }
+
+private:
+    friend std::hash<android::Fps>;
+
+    constexpr Fps(float fps, nsecs_t period) : fps(fps), period(period) {}
+
+    float getBucket() const { return std::round(fps / kMargin); }
+
+    static constexpr float kMargin = 0.001f;
+    float fps = 0;
+    nsecs_t period = 0;
+};
+
+static_assert(std::is_trivially_copyable_v<Fps>);
+
+} // namespace android
+
+namespace std {
+template <>
+struct hash<android::Fps> {
+    std::size_t operator()(const android::Fps& fps) const {
+        return std::hash<float>()(fps.getBucket());
+    }
+};
+} // namespace std
\ No newline at end of file
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 90396dd..949227b 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -1425,7 +1425,8 @@
     // First traverse the tree and count how many layers has votes
     int layersWithVote = 0;
     traverseTree([&layersWithVote](Layer* layer) {
-        const auto layerVotedWithDefaultCompatibility = layer->mCurrentState.frameRate.rate > 0 &&
+        const auto layerVotedWithDefaultCompatibility =
+                layer->mCurrentState.frameRate.rate.isValid() &&
                 layer->mCurrentState.frameRate.type == FrameRateCompatibility::Default;
         const auto layerVotedWithNoVote =
                 layer->mCurrentState.frameRate.type == FrameRateCompatibility::NoVote;
@@ -1486,14 +1487,14 @@
 
 Layer::FrameRate Layer::getFrameRateForLayerTree() const {
     const auto frameRate = getDrawingState().frameRate;
-    if (frameRate.rate > 0 || frameRate.type == FrameRateCompatibility::NoVote) {
+    if (frameRate.rate.isValid() || frameRate.type == FrameRateCompatibility::NoVote) {
         return frameRate;
     }
 
     // This layer doesn't have a frame rate. If one of its ancestors or successors
     // have a vote, return a NoVote for ancestors/successors to set the vote
     if (getDrawingState().treeHasFrameRateVote) {
-        return {0, FrameRateCompatibility::NoVote};
+        return {Fps(0.0f), FrameRateCompatibility::NoVote};
     }
 
     return frameRate;
@@ -1687,9 +1688,9 @@
     const FloatRect& crop = outputLayerState.sourceCrop;
     StringAppendF(&result, "%6.1f %6.1f %6.1f %6.1f | ", crop.left, crop.top, crop.right,
                   crop.bottom);
-    if (layerState.frameRate.rate != 0 ||
+    if (layerState.frameRate.rate.isValid() ||
         layerState.frameRate.type != FrameRateCompatibility::Default) {
-        StringAppendF(&result, "% 6.2ffps %15s seamless=%s", layerState.frameRate.rate,
+        StringAppendF(&result, "%s %15s seamless=%s", to_string(layerState.frameRate.rate).c_str(),
                       frameRateCompatibilityString(layerState.frameRate.type).c_str(),
                       toString(layerState.frameRate.seamlessness).c_str());
     } else {
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 75d68a1..e9fd550 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -45,6 +45,7 @@
 #include "ClientCache.h"
 #include "DisplayHardware/ComposerHal.h"
 #include "DisplayHardware/HWComposer.h"
+#include "Fps.h"
 #include "FrameTracker.h"
 #include "LayerVector.h"
 #include "MonitoredProducer.h"
@@ -155,7 +156,7 @@
     struct FrameRate {
         using Seamlessness = scheduler::Seamlessness;
 
-        float rate;
+        Fps rate;
         FrameRateCompatibility type;
         Seamlessness seamlessness;
 
@@ -163,11 +164,12 @@
               : rate(0),
                 type(FrameRateCompatibility::Default),
                 seamlessness(Seamlessness::Default) {}
-        FrameRate(float rate, FrameRateCompatibility type, bool shouldBeSeamless = true)
+        FrameRate(Fps rate, FrameRateCompatibility type, bool shouldBeSeamless = true)
               : rate(rate), type(type), seamlessness(getSeamlessness(rate, shouldBeSeamless)) {}
 
         bool operator==(const FrameRate& other) const {
-            return rate == other.rate && type == other.type && seamlessness == other.seamlessness;
+            return rate.equalsWithMargin(other.rate) && type == other.type &&
+                    seamlessness == other.seamlessness;
         }
 
         bool operator!=(const FrameRate& other) const { return !(*this == other); }
@@ -177,8 +179,8 @@
         static FrameRateCompatibility convertCompatibility(int8_t compatibility);
 
     private:
-        static Seamlessness getSeamlessness(float rate, bool shouldBeSeamless) {
-            if (rate == 0.0f) {
+        static Seamlessness getSeamlessness(Fps rate, bool shouldBeSeamless) {
+            if (!rate.isValid()) {
                 // Refresh rate of 0 is a special value which should reset the vote to
                 // its default value.
                 return Seamlessness::Default;
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index f676d5b..f99d54a 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -190,7 +190,7 @@
 
     Mutex::Autolock _l(mFlinger.mStateLock);
     mLayer = mClient->getLayerUser(mIBinder);
-    mLayer->setFrameRate(Layer::FrameRate(0, Layer::FrameRateCompatibility::NoVote));
+    mLayer->setFrameRate(Layer::FrameRate(Fps(0.0f), Layer::FrameRateCompatibility::NoVote));
 
     // setting Layer's Z requires resorting layersSortedByZ
     ssize_t idx = mFlinger.mCurrentState.layersSortedByZ.indexOf(mLayer);
@@ -205,7 +205,7 @@
 void RefreshRateOverlay::primeCache() {
     auto& allRefreshRates = mFlinger.mRefreshRateConfigs->getAllRefreshRates();
     if (allRefreshRates.size() == 1) {
-        auto fps = allRefreshRates.begin()->second->getFps();
+        int fps = allRefreshRates.begin()->second->getFps().getIntValue();
         half4 color = {LOW_FPS_COLOR, ALPHA};
         mBufferCache.emplace(fps, SevenSegmentDrawer::drawNumber(fps, color, mShowSpinner));
         return;
@@ -214,7 +214,7 @@
     std::vector<uint32_t> supportedFps;
     supportedFps.reserve(allRefreshRates.size());
     for (auto& [ignored, refreshRate] : allRefreshRates) {
-        supportedFps.push_back(refreshRate->getFps());
+        supportedFps.push_back(refreshRate->getFps().getIntValue());
     }
 
     std::sort(supportedFps.begin(), supportedFps.end());
@@ -240,7 +240,7 @@
 }
 
 void RefreshRateOverlay::changeRefreshRate(const RefreshRate& refreshRate) {
-    mCurrentFps = refreshRate.getFps();
+    mCurrentFps = refreshRate.getFps().getIntValue();
     auto buffer = mBufferCache[*mCurrentFps][mFrame];
     mLayer->setBuffer(buffer, Fence::NO_FENCE, 0, 0, {},
                       mLayer->getHeadFrameNumber(-1 /* expectedPresentTime */));
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index c0d00f3..499daad 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -41,7 +41,7 @@
 
 bool isLayerActive(const Layer& layer, const LayerInfo& info, nsecs_t threshold) {
     // Layers with an explicit vote are always kept active
-    if (layer.getFrameRateForLayerTree().rate > 0) {
+    if (layer.getFrameRateForLayerTree().rate.isValid()) {
         return true;
     }
 
@@ -86,9 +86,8 @@
 
 LayerHistory::~LayerHistory() = default;
 
-void LayerHistory::registerLayer(Layer* layer, float /*lowRefreshRate*/, float highRefreshRate,
-                                 LayerVoteType type) {
-    const nsecs_t highRefreshRatePeriod = static_cast<nsecs_t>(1e9f / highRefreshRate);
+void LayerHistory::registerLayer(Layer* layer, Fps highRefreshRate, LayerVoteType type) {
+    const nsecs_t highRefreshRatePeriod = highRefreshRate.getPeriodNsecs();
     auto info = std::make_unique<LayerInfo>(layer->getName(), highRefreshRatePeriod, type);
     std::lock_guard lock(mLock);
     mLayerInfos.emplace_back(layer, std::move(info));
@@ -148,7 +147,7 @@
                 {strong->getName(), vote.type, vote.fps, vote.seamlessness, weight, layerFocused});
 
         if (CC_UNLIKELY(mTraceEnabled)) {
-            trace(layer, *info, vote.type, static_cast<int>(std::round(vote.fps)));
+            trace(layer, *info, vote.type, vote.fps.getIntValue());
         }
     }
 
@@ -177,7 +176,7 @@
                 }
             }();
 
-            if (frameRate.rate > 0 || voteType == LayerVoteType::NoVote) {
+            if (frameRate.rate.isValid() || voteType == LayerVoteType::NoVote) {
                 const auto type = layer->isVisible() ? voteType : LayerVoteType::NoVote;
                 info->setLayerVote({type, frameRate.rate, frameRate.seamlessness});
             } else {
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index 507ccc6..4214bab 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -46,7 +46,7 @@
     ~LayerHistory();
 
     // Layers are unregistered when the weak reference expires.
-    void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate, LayerVoteType type);
+    void registerLayer(Layer*, Fps highRefreshRate, LayerVoteType type);
 
     // Sets the display size. Client is responsible for synchronization.
     void setDisplayArea(uint32_t displayArea) { mDisplayArea = displayArea; }
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 66ac98a..1c0065c 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -38,7 +38,7 @@
       : mName(name),
         mHighRefreshRatePeriod(highRefreshRatePeriod),
         mDefaultVote(defaultVote),
-        mLayerVote({defaultVote, 0.0f}),
+        mLayerVote({defaultVote, Fps(0.0f)}),
         mRefreshRateHistory(name) {}
 
 void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType,
@@ -91,7 +91,8 @@
 
     // Layer is considered frequent if the average frame rate is higher than the threshold
     const auto totalTime = mFrameTimes.back().queueTime - it->queueTime;
-    return (1e9f * (numFrames - 1)) / totalTime >= MIN_FPS_FOR_FREQUENT_LAYER;
+    return Fps::fromPeriodNsecs(totalTime / (numFrames - 1))
+            .greaterThanOrEqualWithMargin(MIN_FPS_FOR_FREQUENT_LAYER);
 }
 
 bool LayerInfo::isAnimating(nsecs_t now) const {
@@ -139,7 +140,7 @@
             missingPresentTime = true;
             // If there are no presentation timestamps and we haven't calculated
             // one in the past then we can't calculate the refresh rate
-            if (mLastRefreshRate.reported == 0) {
+            if (!mLastRefreshRate.reported.isValid()) {
                 return std::nullopt;
             }
             continue;
@@ -163,7 +164,7 @@
     return static_cast<nsecs_t>(averageFrameTime);
 }
 
-std::optional<float> LayerInfo::calculateRefreshRateIfPossible(nsecs_t now) {
+std::optional<Fps> LayerInfo::calculateRefreshRateIfPossible(nsecs_t now) {
     static constexpr float MARGIN = 1.0f; // 1Hz
     if (!hasEnoughDataForHeuristic()) {
         ALOGV("Not enough data");
@@ -172,7 +173,7 @@
 
     const auto averageFrameTime = calculateAverageFrameTime();
     if (averageFrameTime.has_value()) {
-        const auto refreshRate = 1e9f / *averageFrameTime;
+        const auto refreshRate = Fps::fromPeriodNsecs(*averageFrameTime);
         const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);
         if (refreshRateConsistent) {
             const auto knownRefreshRate =
@@ -180,22 +181,23 @@
 
             // To avoid oscillation, use the last calculated refresh rate if it is
             // close enough
-            if (std::abs(mLastRefreshRate.calculated - refreshRate) > MARGIN &&
-                mLastRefreshRate.reported != knownRefreshRate) {
+            if (std::abs(mLastRefreshRate.calculated.getValue() - refreshRate.getValue()) >
+                        MARGIN &&
+                !mLastRefreshRate.reported.equalsWithMargin(knownRefreshRate)) {
                 mLastRefreshRate.calculated = refreshRate;
                 mLastRefreshRate.reported = knownRefreshRate;
             }
 
-            ALOGV("%s %.2fHz rounded to nearest known frame rate %.2fHz", mName.c_str(),
-                  refreshRate, mLastRefreshRate.reported);
+            ALOGV("%s %s rounded to nearest known frame rate %s", mName.c_str(),
+                  to_string(refreshRate).c_str(), to_string(mLastRefreshRate.reported).c_str());
         } else {
-            ALOGV("%s Not stable (%.2fHz) returning last known frame rate %.2fHz", mName.c_str(),
-                  refreshRate, mLastRefreshRate.reported);
+            ALOGV("%s Not stable (%s) returning last known frame rate %s", mName.c_str(),
+                  to_string(refreshRate).c_str(), to_string(mLastRefreshRate.reported).c_str());
         }
     }
 
-    return mLastRefreshRate.reported == 0 ? std::nullopt
-                                          : std::make_optional(mLastRefreshRate.reported);
+    return mLastRefreshRate.reported.isValid() ? std::make_optional(mLastRefreshRate.reported)
+                                               : std::nullopt;
 }
 
 LayerInfo::LayerVote LayerInfo::getRefreshRateVote(nsecs_t now) {
@@ -207,13 +209,13 @@
     if (isAnimating(now)) {
         ALOGV("%s is animating", mName.c_str());
         mLastRefreshRate.animatingOrInfrequent = true;
-        return {LayerHistory::LayerVoteType::Max, 0};
+        return {LayerHistory::LayerVoteType::Max, Fps(0.0f)};
     }
 
     if (!isFrequent(now)) {
         ALOGV("%s is infrequent", mName.c_str());
         mLastRefreshRate.animatingOrInfrequent = true;
-        return {LayerHistory::LayerVoteType::Min, 0};
+        return {LayerHistory::LayerVoteType::Min, Fps(0.0f)};
     }
 
     // If the layer was previously tagged as animating or infrequent, we clear
@@ -225,12 +227,12 @@
 
     auto refreshRate = calculateRefreshRateIfPossible(now);
     if (refreshRate.has_value()) {
-        ALOGV("%s calculated refresh rate: %.2f", mName.c_str(), refreshRate.value());
+        ALOGV("%s calculated refresh rate: %s", mName.c_str(), to_string(*refreshRate).c_str());
         return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
     }
 
     ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());
-    return {LayerHistory::LayerVoteType::Max, 0};
+    return {LayerHistory::LayerVoteType::Max, Fps(0.0f)};
 }
 
 const char* LayerInfo::getTraceTag(android::scheduler::LayerHistory::LayerVoteType type) const {
@@ -256,7 +258,7 @@
     mRefreshRates.clear();
 }
 
-bool LayerInfo::RefreshRateHistory::add(float refreshRate, nsecs_t now) {
+bool LayerInfo::RefreshRateHistory::add(Fps refreshRate, nsecs_t now) {
     mRefreshRates.push_back({refreshRate, now});
     while (mRefreshRates.size() >= HISTORY_SIZE ||
            now - mRefreshRates.front().timestamp > HISTORY_DURATION.count()) {
@@ -268,7 +270,7 @@
             mHeuristicTraceTagData = makeHeuristicTraceTagData();
         }
 
-        ATRACE_INT(mHeuristicTraceTagData->average.c_str(), static_cast<int>(refreshRate));
+        ATRACE_INT(mHeuristicTraceTagData->average.c_str(), refreshRate.getIntValue());
     }
 
     return isConsistent();
@@ -279,15 +281,16 @@
 
     const auto max = std::max_element(mRefreshRates.begin(), mRefreshRates.end());
     const auto min = std::min_element(mRefreshRates.begin(), mRefreshRates.end());
-    const auto consistent = max->refreshRate - min->refreshRate <= MARGIN_FPS;
+    const auto consistent =
+            max->refreshRate.getValue() - min->refreshRate.getValue() < MARGIN_CONSISTENT_FPS;
 
     if (CC_UNLIKELY(sTraceEnabled)) {
         if (!mHeuristicTraceTagData.has_value()) {
             mHeuristicTraceTagData = makeHeuristicTraceTagData();
         }
 
-        ATRACE_INT(mHeuristicTraceTagData->max.c_str(), static_cast<int>(max->refreshRate));
-        ATRACE_INT(mHeuristicTraceTagData->min.c_str(), static_cast<int>(min->refreshRate));
+        ATRACE_INT(mHeuristicTraceTagData->max.c_str(), max->refreshRate.getIntValue());
+        ATRACE_INT(mHeuristicTraceTagData->min.c_str(), min->refreshRate.getIntValue());
         ATRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
     }
 
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index e434670..9304e62 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -50,9 +50,9 @@
     // 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 float MIN_FPS_FOR_FREQUENT_LAYER = 10.0f;
+    static constexpr Fps MIN_FPS_FOR_FREQUENT_LAYER{10.0f};
     static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS =
-            std::chrono::nanoseconds(static_cast<nsecs_t>(1e9f / MIN_FPS_FOR_FREQUENT_LAYER)) + 1ms;
+            std::chrono::nanoseconds(MIN_FPS_FOR_FREQUENT_LAYER.getPeriodNsecs()) + 1ms;
 
     friend class LayerHistoryTest;
 
@@ -60,7 +60,7 @@
     // Holds information about the layer vote
     struct LayerVote {
         LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
-        float fps = 0.0f;
+        Fps fps{0.0f};
         Seamlessness seamlessness = Seamlessness::Default;
     };
 
@@ -92,7 +92,7 @@
     void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; }
 
     // Resets the layer vote to its default.
-    void resetLayerVote() { mLayerVote = {mDefaultVote, 0.0f, Seamlessness::Default}; }
+    void resetLayerVote() { mLayerVote = {mDefaultVote, Fps(0.0f), Seamlessness::Default}; }
 
     LayerVote getRefreshRateVote(nsecs_t now);
 
@@ -130,9 +130,9 @@
     // Holds information about the calculated and reported refresh rate
     struct RefreshRateHeuristicData {
         // Rate calculated on the layer
-        float calculated = 0.0f;
+        Fps calculated{0.0f};
         // Last reported rate for LayerInfo::getRefreshRate()
-        float reported = 0.0f;
+        Fps reported{0.0f};
         // Whether the last reported rate for LayerInfo::getRefreshRate()
         // was due to animation or infrequent updates
         bool animatingOrInfrequent = false;
@@ -151,18 +151,20 @@
         void clear();
 
         // Adds a new refresh rate and returns true if it is consistent
-        bool add(float refreshRate, nsecs_t now);
+        bool add(Fps refreshRate, nsecs_t now);
 
     private:
         friend class LayerHistoryTest;
 
         // Holds the refresh rate when it was calculated
         struct RefreshRateData {
-            float refreshRate = 0.0f;
+            Fps refreshRate{0.0f};
             nsecs_t timestamp = 0;
 
             bool operator<(const RefreshRateData& other) const {
-                return refreshRate < other.refreshRate;
+                // 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();
             }
         };
 
@@ -180,13 +182,13 @@
         const std::string mName;
         mutable std::optional<HeuristicTraceTagData> mHeuristicTraceTagData;
         std::deque<RefreshRateData> mRefreshRates;
-        static constexpr float MARGIN_FPS = 1.0;
+        static constexpr float MARGIN_CONSISTENT_FPS = 1.0;
     };
 
     bool isFrequent(nsecs_t now) const;
     bool isAnimating(nsecs_t now) const;
     bool hasEnoughDataForHeuristic() const;
-    std::optional<float> calculateRefreshRateIfPossible(nsecs_t now);
+    std::optional<Fps> calculateRefreshRateIfPossible(nsecs_t now);
     std::optional<nsecs_t> calculateAverageFrameTime() const;
     bool isFrameTimeValid(const FrameTimeData&) const;
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 83fa20e..4b7251b 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -29,10 +29,10 @@
 namespace android::scheduler {
 namespace {
 std::string formatLayerInfo(const RefreshRateConfigs::LayerRequirement& layer, float weight) {
-    return base::StringPrintf("%s (type=%s, weight=%.2f seamlessness=%s) %.2fHz",
-                              layer.name.c_str(),
+    return base::StringPrintf("%s (type=%s, weight=%.2f seamlessness=%s) %s", layer.name.c_str(),
                               RefreshRateConfigs::layerVoteTypeString(layer.vote).c_str(), weight,
-                              toString(layer.seamlessness).c_str(), layer.desiredRefreshRate);
+                              toString(layer.seamlessness).c_str(),
+                              to_string(layer.desiredRefreshRate).c_str());
 }
 } // namespace
 
@@ -41,7 +41,7 @@
 
 std::string RefreshRate::toString() const {
     return base::StringPrintf("{id=%d, hwcId=%d, fps=%.2f, width=%d, height=%d group=%d}",
-                              getConfigId().value(), hwcConfig->getId(), getFps(),
+                              getConfigId().value(), hwcConfig->getId(), getFps().getValue(),
                               hwcConfig->getWidth(), hwcConfig->getHeight(), getConfigGroup());
 }
 
@@ -64,9 +64,9 @@
 
 std::string RefreshRateConfigs::Policy::toString() const {
     return base::StringPrintf("default config ID: %d, allowGroupSwitching = %d"
-                              ", primary range: [%.2f %.2f], app request range: [%.2f %.2f]",
-                              defaultConfig.value(), allowGroupSwitching, primaryRange.min,
-                              primaryRange.max, appRequestRange.min, appRequestRange.max);
+                              ", primary range: %s, app request range: %s",
+                              defaultConfig.value(), allowGroupSwitching,
+                              primaryRange.toString().c_str(), appRequestRange.toString().c_str());
 }
 
 std::pair<nsecs_t, nsecs_t> RefreshRateConfigs::getDisplayFrames(nsecs_t layerPeriod,
@@ -144,7 +144,8 @@
     // move out the of range if layers explicitly request a different refresh
     // rate.
     const Policy* policy = getCurrentPolicyLocked();
-    const bool primaryRangeIsSingleRate = policy->primaryRange.min == policy->primaryRange.max;
+    const bool primaryRangeIsSingleRate =
+            policy->primaryRange.min.equalsWithMargin(policy->primaryRange.max);
 
     if (!globalSignals.touch && globalSignals.idle &&
         !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
@@ -229,17 +230,18 @@
 
             // If the layer wants Max, give higher score to the higher refresh rate
             if (layer.vote == LayerVoteType::Max) {
-                const auto ratio = scores[i].first->fps / scores.back().first->fps;
+                const auto ratio =
+                        scores[i].first->fps.getValue() / scores.back().first->fps.getValue();
                 // use ratio^2 to get a lower score the more we get further from peak
                 const auto layerScore = ratio * ratio;
                 ALOGV("%s gives %s score of %.2f", formatLayerInfo(layer, weight).c_str(),
-                      scores[i].first->name.c_str(), layerScore);
+                      scores[i].first->getName().c_str(), layerScore);
                 scores[i].second += weight * layerScore;
                 continue;
             }
 
             const auto displayPeriod = scores[i].first->hwcConfig->getVsyncPeriod();
-            const auto layerPeriod = round<nsecs_t>(1e9f / layer.desiredRefreshRate);
+            const auto layerPeriod = layer.desiredRefreshRate.getPeriodNsecs();
             if (layer.vote == LayerVoteType::ExplicitDefault) {
                 const auto layerScore = [&]() {
                     // Find the actual rate the layer will render, assuming
@@ -256,7 +258,7 @@
                 }();
 
                 ALOGV("%s gives %s score of %.2f", formatLayerInfo(layer, weight).c_str(),
-                      scores[i].first->name.c_str(), layerScore);
+                      scores[i].first->getName().c_str(), layerScore);
                 scores[i].second += weight * layerScore;
                 continue;
             }
@@ -297,7 +299,7 @@
                 constexpr float kSeamedSwitchPenalty = 0.95f;
                 const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty;
                 ALOGV("%s gives %s score of %.2f", formatLayerInfo(layer, weight).c_str(),
-                      scores[i].first->name.c_str(), layerScore);
+                      scores[i].first->getName().c_str(), layerScore);
                 scores[i].second += weight * layerScore * seamlessness;
                 continue;
             }
@@ -331,7 +333,7 @@
     const RefreshRate& touchRefreshRate = getMaxRefreshRateByPolicyLocked();
 
     if (globalSignals.touch && explicitDefaultVoteLayers == 0 &&
-        bestRefreshRate->fps < touchRefreshRate.fps) {
+        bestRefreshRate->fps.lessThanWithMargin(touchRefreshRate.fps)) {
         setTouchConsidered();
         ALOGV("TouchBoost - choose %s", touchRefreshRate.getName().c_str());
         return touchRefreshRate;
@@ -347,9 +349,9 @@
     float max = begin->second;
     for (auto i = begin; i != end; ++i) {
         const auto [refreshRate, score] = *i;
-        ALOGV("%s scores %.2f", refreshRate->name.c_str(), score);
+        ALOGV("%s scores %.2f", refreshRate->getName().c_str(), score);
 
-        ATRACE_INT(refreshRate->name.c_str(), round<int>(score * 100));
+        ATRACE_INT(refreshRate->getName().c_str(), round<int>(score * 100));
 
         if (score > max * (1 + EPSILON)) {
             max = score;
@@ -433,10 +435,10 @@
 
     for (auto configId = HwcConfigIndexType(0); configId.value() < configs.size(); configId++) {
         const auto& config = configs.at(static_cast<size_t>(configId.value()));
-        const float fps = 1e9f / config->getVsyncPeriod();
         mRefreshRates.emplace(configId,
                               std::make_unique<RefreshRate>(configId, config,
-                                                            base::StringPrintf("%.2ffps", fps), fps,
+                                                            Fps::fromPeriodNsecs(
+                                                                    config->getVsyncPeriod()),
                                                             RefreshRate::ConstructorTag(0)));
         if (configId == currentConfigId) {
             mCurrentRefreshRate = mRefreshRates.at(configId).get();
@@ -463,8 +465,8 @@
         ALOGE("Default config is not in the primary range.");
         return false;
     }
-    return policy.appRequestRange.min <= policy.primaryRange.min &&
-            policy.appRequestRange.max >= policy.primaryRange.max;
+    return policy.appRequestRange.min.lessThanOrEqualWithMargin(policy.primaryRange.min) &&
+            policy.appRequestRange.max.greaterThanOrEqualWithMargin(policy.primaryRange.max);
 }
 
 status_t RefreshRateConfigs::setDisplayManagerPolicy(const Policy& policy) {
@@ -550,12 +552,9 @@
     // Filter configs based on current policy and sort based on vsync period
     const Policy* policy = getCurrentPolicyLocked();
     const auto& defaultConfig = mRefreshRates.at(policy->defaultConfig)->hwcConfig;
-    ALOGV("constructAvailableRefreshRates: default %d group %d primaryRange=[%.2f %.2f]"
-          " appRequestRange=[%.2f %.2f]",
-          policy->defaultConfig.value(), defaultConfig->getConfigGroup(), policy->primaryRange.min,
-          policy->primaryRange.max, policy->appRequestRange.min, policy->appRequestRange.max);
+    ALOGV("constructAvailableRefreshRates: %s ", policy->toString().c_str());
 
-    auto filterRefreshRates = [&](float min, float max, const char* listName,
+    auto filterRefreshRates = [&](Fps min, Fps max, const char* listName,
                                   std::vector<const RefreshRate*>* outRefreshRates) {
         getSortedRefreshRateList(
                 [&](const RefreshRate& refreshRate) REQUIRES(mLock) {
@@ -572,12 +571,12 @@
                 outRefreshRates);
 
         LOG_ALWAYS_FATAL_IF(outRefreshRates->empty(),
-                            "No matching configs for %s range: min=%.0f max=%.0f", listName, min,
-                            max);
+                            "No matching configs for %s range: min=%s max=%s", listName,
+                            to_string(min).c_str(), to_string(max).c_str());
         auto stringifyRefreshRates = [&]() -> std::string {
             std::string str;
             for (auto refreshRate : *outRefreshRates) {
-                base::StringAppendF(&str, "%s ", refreshRate->name.c_str());
+                base::StringAppendF(&str, "%s ", refreshRate->getName().c_str());
             }
             return str;
         };
@@ -590,39 +589,39 @@
                        &mAppRequestRefreshRates);
 }
 
-std::vector<float> RefreshRateConfigs::constructKnownFrameRates(
+std::vector<Fps> RefreshRateConfigs::constructKnownFrameRates(
         const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs) {
-    std::vector<float> knownFrameRates = {24.0f, 30.0f, 45.0f, 60.0f, 72.0f};
+    std::vector<Fps> knownFrameRates = {Fps(24.0f), Fps(30.0f), Fps(45.0f), Fps(60.0f), Fps(72.0f)};
     knownFrameRates.reserve(knownFrameRates.size() + configs.size());
 
     // Add all supported refresh rates to the set
     for (const auto& config : configs) {
-        const auto refreshRate = 1e9f / config->getVsyncPeriod();
+        const auto refreshRate = Fps::fromPeriodNsecs(config->getVsyncPeriod());
         knownFrameRates.emplace_back(refreshRate);
     }
 
     // Sort and remove duplicates
-    const auto frameRatesEqual = [](float a, float b) { return std::abs(a - b) <= 0.01f; };
-    std::sort(knownFrameRates.begin(), knownFrameRates.end());
+    std::sort(knownFrameRates.begin(), knownFrameRates.end(), Fps::comparesLess);
     knownFrameRates.erase(std::unique(knownFrameRates.begin(), knownFrameRates.end(),
-                                      frameRatesEqual),
+                                      Fps::EqualsWithMargin()),
                           knownFrameRates.end());
     return knownFrameRates;
 }
 
-float RefreshRateConfigs::findClosestKnownFrameRate(float frameRate) const {
-    if (frameRate <= *mKnownFrameRates.begin()) {
+Fps RefreshRateConfigs::findClosestKnownFrameRate(Fps frameRate) const {
+    if (frameRate.lessThanOrEqualWithMargin(*mKnownFrameRates.begin())) {
         return *mKnownFrameRates.begin();
     }
 
-    if (frameRate >= *std::prev(mKnownFrameRates.end())) {
+    if (frameRate.greaterThanOrEqualWithMargin(*std::prev(mKnownFrameRates.end()))) {
         return *std::prev(mKnownFrameRates.end());
     }
 
-    auto lowerBound = std::lower_bound(mKnownFrameRates.begin(), mKnownFrameRates.end(), frameRate);
+    auto lowerBound = std::lower_bound(mKnownFrameRates.begin(), mKnownFrameRates.end(), frameRate,
+                                       Fps::comparesLess);
 
-    const auto distance1 = std::abs(frameRate - *lowerBound);
-    const auto distance2 = std::abs(frameRate - *std::prev(lowerBound));
+    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);
 }
 
@@ -657,7 +656,7 @@
 
     std::lock_guard lock(mLock);
     if (frameRateOverride.frameRateHz != 0) {
-        mPreferredRefreshRateForUid[frameRateOverride.uid] = frameRateOverride.frameRateHz;
+        mPreferredRefreshRateForUid[frameRateOverride.uid] = Fps(frameRateOverride.frameRateHz);
     } else {
         mPreferredRefreshRateForUid.erase(frameRateOverride.uid);
     }
@@ -675,7 +674,7 @@
     // in DisplayManagerService.getDisplayInfoForFrameRateOverride
     constexpr float kThreshold = 0.1f;
     const auto refreshRateHz = iter->second;
-    const auto numPeriods = mCurrentRefreshRate->getFps() / refreshRateHz;
+    const auto numPeriods = mCurrentRefreshRate->getFps().getValue() / refreshRateHz.getValue();
     const auto numPeriodsRounded = std::round(numPeriods);
     if (std::abs(numPeriods - numPeriodsRounded) > kThreshold) {
         return 1;
@@ -690,7 +689,7 @@
     overrides.reserve(mPreferredRefreshRateForUid.size());
 
     for (const auto [uid, frameRate] : mPreferredRefreshRateForUid) {
-        overrides.emplace_back(FrameRateOverride{uid, frameRate});
+        overrides.emplace_back(FrameRateOverride{uid, frameRate.getValue()});
     }
 
     return overrides;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 6e0c0d3..ec7ffe5 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -25,6 +25,7 @@
 #include <type_traits>
 
 #include "DisplayHardware/HWComposer.h"
+#include "Fps.h"
 #include "HwcStrongTypes.h"
 #include "Scheduler/SchedulerUtils.h"
 #include "Scheduler/Seamlessness.h"
@@ -64,29 +65,31 @@
 
     public:
         RefreshRate(HwcConfigIndexType configId,
-                    std::shared_ptr<const HWC2::Display::Config> config, std::string name,
-                    float fps, ConstructorTag)
-              : configId(configId), hwcConfig(config), name(std::move(name)), fps(fps) {}
+                    std::shared_ptr<const HWC2::Display::Config> config, Fps fps, ConstructorTag)
+              : configId(configId), hwcConfig(config), fps(std::move(fps)) {}
 
         RefreshRate(const RefreshRate&) = delete;
 
         HwcConfigIndexType getConfigId() const { return configId; }
         nsecs_t getVsyncPeriod() const { return hwcConfig->getVsyncPeriod(); }
         int32_t getConfigGroup() const { return hwcConfig->getConfigGroup(); }
-        const std::string& getName() const { return name; }
-        float getFps() const { return fps; }
+        std::string getName() const { return to_string(fps); }
+        Fps getFps() const { return fps; }
 
         // Checks whether the fps of this RefreshRate struct is within a given min and max refresh
-        // rate passed in. FPS_EPSILON is applied to the boundaries for approximation.
-        bool inPolicy(float minRefreshRate, float maxRefreshRate) const {
-            return (fps >= (minRefreshRate - FPS_EPSILON) && fps <= (maxRefreshRate + FPS_EPSILON));
+        // rate passed in. Margin of error is applied to the boundaries for approximation.
+        bool inPolicy(Fps minRefreshRate, Fps maxRefreshRate) const {
+            return minRefreshRate.lessThanOrEqualWithMargin(fps) &&
+                    fps.lessThanOrEqualWithMargin(maxRefreshRate);
         }
 
         bool operator!=(const RefreshRate& other) const {
             return configId != other.configId || hwcConfig != other.hwcConfig;
         }
 
-        bool operator<(const RefreshRate& other) const { return getFps() < other.getFps(); }
+        bool operator<(const RefreshRate& other) const {
+            return getFps().getValue() < other.getFps().getValue();
+        }
 
         bool operator==(const RefreshRate& other) const { return !(*this != other); }
 
@@ -96,18 +99,13 @@
         friend RefreshRateConfigs;
         friend class RefreshRateConfigsTest;
 
-        // The tolerance within which we consider FPS approximately equals.
-        static constexpr float FPS_EPSILON = 0.001f;
-
         // This config ID corresponds to the position of the config in the vector that is stored
         // on the device.
         const HwcConfigIndexType configId;
         // The config itself
         std::shared_ptr<const HWC2::Display::Config> hwcConfig;
-        // Human readable name of the refresh rate.
-        const std::string name;
         // Refresh rate in frames per second
-        const float fps = 0;
+        const Fps fps{0.0f};
     };
 
     using AllRefreshRatesMapType =
@@ -119,14 +117,19 @@
 
     public:
         struct Range {
-            float min = 0;
-            float max = std::numeric_limits<float>::max();
+            Fps min{0.0f};
+            Fps max{std::numeric_limits<float>::max()};
 
             bool operator==(const Range& other) const {
-                return min == other.min && max == other.max;
+                return min.equalsWithMargin(other.min) && max.equalsWithMargin(other.max);
             }
 
             bool operator!=(const Range& other) const { return !(*this == other); }
+
+            std::string toString() const {
+                return base::StringPrintf("[%s %s]", to_string(min).c_str(),
+                                          to_string(max).c_str());
+            }
         };
 
         // The default config, used to ensure we only initiate display config switches within the
@@ -221,7 +224,7 @@
         // Layer vote type.
         LayerVoteType vote = LayerVoteType::NoVote;
         // Layer's desired refresh rate, if applicable.
-        float desiredRefreshRate = 0.0f;
+        Fps desiredRefreshRate{0.0f};
         // 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 +235,7 @@
 
         bool operator==(const LayerRequirement& other) const {
             return name == other.name && vote == other.vote &&
-                    desiredRefreshRate == other.desiredRefreshRate &&
+                    desiredRefreshRate.equalsWithMargin(other.desiredRefreshRate) &&
                     seamlessness == other.seamlessness && weight == other.weight &&
                     focused == other.focused;
         }
@@ -295,7 +298,7 @@
     static std::string layerVoteTypeString(LayerVoteType vote);
 
     // Returns a known frame rate that is the closest to frameRate
-    float findClosestKnownFrameRate(float frameRate) const;
+    Fps findClosestKnownFrameRate(Fps frameRate) const;
 
     RefreshRateConfigs(const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
                        HwcConfigIndexType currentConfigId);
@@ -332,7 +335,7 @@
     friend class RefreshRateConfigsTest;
 
     void constructAvailableRefreshRates() REQUIRES(mLock);
-    static std::vector<float> constructKnownFrameRates(
+    static std::vector<Fps> constructKnownFrameRates(
             const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs);
 
     void getSortedRefreshRateList(
@@ -387,7 +390,7 @@
 
     // A mapping between a UID and a preferred refresh rate that this app would
     // run at.
-    std::unordered_map<uid_t, float> mPreferredRefreshRateForUid GUARDED_BY(mLock);
+    std::unordered_map<uid_t, Fps> mPreferredRefreshRateForUid GUARDED_BY(mLock);
 
     // The min and max refresh rates supported by the device.
     // This will not change at runtime.
@@ -398,7 +401,7 @@
 
     // A sorted list of known frame rates that a Heuristic layer will choose
     // from based on the closest value.
-    const std::vector<float> mKnownFrameRates;
+    const std::vector<Fps> mKnownFrameRates;
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateStats.h b/services/surfaceflinger/Scheduler/RefreshRateStats.h
index d9e7b37..ce91a76 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateStats.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateStats.h
@@ -115,8 +115,10 @@
                 mConfigModesTotalTime[mCurrentConfigMode] = 0;
             }
             mConfigModesTotalTime[mCurrentConfigMode] += timeElapsedMs;
-            fps = static_cast<uint32_t>(std::round(
-                    mRefreshRateConfigs.getRefreshRateFromConfigId(mCurrentConfigMode).getFps()));
+            fps = static_cast<uint32_t>(
+                    std::round(mRefreshRateConfigs.getRefreshRateFromConfigId(mCurrentConfigMode)
+                                       .getFps()
+                                       .getValue()));
         } else {
             mScreenOffTime += timeElapsedMs;
         }
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 52bf483..07411b0 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -510,25 +510,22 @@
 void Scheduler::registerLayer(Layer* layer) {
     if (!mLayerHistory) return;
 
-    const auto minFps = mRefreshRateConfigs.getMinRefreshRate().getFps();
     const auto maxFps = mRefreshRateConfigs.getMaxRefreshRate().getFps();
 
     if (layer->getWindowType() == InputWindowInfo::Type::STATUS_BAR) {
-        mLayerHistory->registerLayer(layer, minFps, maxFps,
-                                     scheduler::LayerHistory::LayerVoteType::NoVote);
+        mLayerHistory->registerLayer(layer, maxFps, scheduler::LayerHistory::LayerVoteType::NoVote);
     } else if (!mOptions.useContentDetection) {
         // If the content detection feature is off, all layers are registered at Max. We still keep
         // the layer history, since we use it for other features (like Frame Rate API), so layers
         // still need to be registered.
-        mLayerHistory->registerLayer(layer, minFps, maxFps,
-                                     scheduler::LayerHistory::LayerVoteType::Max);
+        mLayerHistory->registerLayer(layer, maxFps, scheduler::LayerHistory::LayerVoteType::Max);
     } else {
         if (layer->getWindowType() == InputWindowInfo::Type::WALLPAPER) {
             // Running Wallpaper at Min is considered as part of content detection.
-            mLayerHistory->registerLayer(layer, minFps, maxFps,
+            mLayerHistory->registerLayer(layer, maxFps,
                                          scheduler::LayerHistory::LayerVoteType::Min);
         } else {
-            mLayerHistory->registerLayer(layer, minFps, maxFps,
+            mLayerHistory->registerLayer(layer, maxFps,
                                          scheduler::LayerHistory::LayerVoteType::Heuristic);
         }
     }
@@ -618,14 +615,15 @@
     // TODO(145561154): cleanup the kernel idle timer implementation and the refresh rate
     // magic number
     const auto& refreshRate = mRefreshRateConfigs.getCurrentRefreshRate();
-    constexpr float FPS_THRESHOLD_FOR_KERNEL_TIMER = 65.0f;
-    if (state == TimerState::Reset && refreshRate.getFps() > FPS_THRESHOLD_FOR_KERNEL_TIMER) {
+    constexpr Fps FPS_THRESHOLD_FOR_KERNEL_TIMER{65.0f};
+    if (state == TimerState::Reset &&
+        refreshRate.getFps().greaterThanWithMargin(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() <= FPS_THRESHOLD_FOR_KERNEL_TIMER) {
+               refreshRate.getFps().lessThanOrEqualWithMargin(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.
diff --git a/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp b/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp
index aac2569..8431323 100644
--- a/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp
@@ -31,15 +31,10 @@
     return std::nullopt;
 }
 
-bool fpsEqualsWithMargin(float fpsA, float fpsB) {
-    static constexpr float MARGIN = 0.01f;
-    return std::abs(fpsA - fpsB) <= MARGIN;
-}
-
-std::vector<float> getRefreshRatesFromConfigs(
+std::vector<android::Fps> getRefreshRatesFromConfigs(
         const android::scheduler::RefreshRateConfigs& refreshRateConfigs) {
     const auto& allRefreshRates = refreshRateConfigs.getAllRefreshRates();
-    std::vector<float> refreshRates;
+    std::vector<android::Fps> refreshRates;
     refreshRates.reserve(allRefreshRates.size());
 
     for (const auto& [ignored, refreshRate] : allRefreshRates) {
@@ -53,12 +48,12 @@
 
 namespace android::scheduler::impl {
 
-VsyncConfiguration::VsyncConfiguration(float currentFps) : mRefreshRateFps(currentFps) {}
+VsyncConfiguration::VsyncConfiguration(Fps currentFps) : mRefreshRateFps(currentFps) {}
 
-PhaseOffsets::VsyncConfigSet VsyncConfiguration::getConfigsForRefreshRate(float fps) const {
+PhaseOffsets::VsyncConfigSet VsyncConfiguration::getConfigsForRefreshRate(Fps fps) const {
     const auto iter = std::find_if(mOffsets.begin(), mOffsets.end(),
-                                   [&fps](const std::pair<float, VsyncConfigSet>& candidateFps) {
-                                       return fpsEqualsWithMargin(fps, candidateFps.first);
+                                   [&fps](const std::pair<Fps, VsyncConfigSet>& candidateFps) {
+                                       return fps.equalsWithMargin(candidateFps.first);
                                    });
 
     if (iter != mOffsets.end()) {
@@ -67,13 +62,13 @@
 
     // Unknown refresh rate. This might happen if we get a hotplug event for an external display.
     // In this case just construct the offset.
-    ALOGW("Can't find offset for %.2f fps", fps);
-    return constructOffsets(static_cast<nsecs_t>(1e9f / fps));
+    ALOGW("Can't find offset for %s", to_string(fps).c_str());
+    return constructOffsets(fps.getPeriodNsecs());
 }
 
-void VsyncConfiguration::initializeOffsets(const std::vector<float>& refreshRates) {
+void VsyncConfiguration::initializeOffsets(const std::vector<Fps>& refreshRates) {
     for (const auto fps : refreshRates) {
-        mOffsets.emplace(fps, constructOffsets(static_cast<nsecs_t>(1e9f / fps)));
+        mOffsets.emplace(fps, constructOffsets(fps.getPeriodNsecs()));
     }
 }
 
@@ -127,7 +122,7 @@
                              .value_or(std::numeric_limits<nsecs_t>::max())) {}
 
 PhaseOffsets::PhaseOffsets(
-        const std::vector<float>& refreshRates, float currentFps, nsecs_t vsyncPhaseOffsetNs,
+        const std::vector<Fps>& refreshRates, Fps currentFps, nsecs_t vsyncPhaseOffsetNs,
         nsecs_t sfVSyncPhaseOffsetNs, std::optional<nsecs_t> earlySfOffsetNs,
         std::optional<nsecs_t> earlyGpuSfOffsetNs, std::optional<nsecs_t> earlyAppOffsetNs,
         std::optional<nsecs_t> earlyGpuAppOffsetNs, nsecs_t highFpsVsyncPhaseOffsetNs,
@@ -378,10 +373,9 @@
     validateSysprops();
 }
 
-WorkDuration::WorkDuration(const std::vector<float>& refreshRates, float currentFps,
-                           nsecs_t sfDuration, nsecs_t appDuration, nsecs_t sfEarlyDuration,
-                           nsecs_t appEarlyDuration, nsecs_t sfEarlyGpuDuration,
-                           nsecs_t appEarlyGpuDuration)
+WorkDuration::WorkDuration(const std::vector<Fps>& refreshRates, Fps currentFps, nsecs_t sfDuration,
+                           nsecs_t appDuration, nsecs_t sfEarlyDuration, nsecs_t appEarlyDuration,
+                           nsecs_t sfEarlyGpuDuration, nsecs_t appEarlyGpuDuration)
       : VsyncConfiguration(currentFps),
         mSfDuration(sfDuration),
         mAppDuration(appDuration),
diff --git a/services/surfaceflinger/Scheduler/VsyncConfiguration.h b/services/surfaceflinger/Scheduler/VsyncConfiguration.h
index c27a25d..a120e97 100644
--- a/services/surfaceflinger/Scheduler/VsyncConfiguration.h
+++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.h
@@ -18,6 +18,9 @@
 
 #include <unordered_map>
 
+#include <utils/Timers.h>
+
+#include "Fps.h"
 #include "RefreshRateConfigs.h"
 #include "VsyncModulator.h"
 
@@ -35,9 +38,9 @@
 
     virtual ~VsyncConfiguration() = default;
     virtual VsyncConfigSet getCurrentConfigs() const = 0;
-    virtual VsyncConfigSet getConfigsForRefreshRate(float fps) const = 0;
+    virtual VsyncConfigSet getConfigsForRefreshRate(Fps fps) const = 0;
 
-    virtual void setRefreshRateFps(float fps) = 0;
+    virtual void setRefreshRateFps(Fps fps) = 0;
 
     virtual void dump(std::string& result) const = 0;
 };
@@ -51,10 +54,10 @@
  */
 class VsyncConfiguration : public scheduler::VsyncConfiguration {
 public:
-    explicit VsyncConfiguration(float currentFps);
+    explicit VsyncConfiguration(Fps currentFps);
 
     // Returns early, early GL, and late offsets for Apps and SF for a given refresh rate.
-    VsyncConfigSet getConfigsForRefreshRate(float fps) const override;
+    VsyncConfigSet getConfigsForRefreshRate(Fps fps) const override;
 
     // Returns early, early GL, and late offsets for Apps and SF.
     VsyncConfigSet getCurrentConfigs() const override {
@@ -63,17 +66,17 @@
 
     // This function should be called when the device is switching between different
     // refresh rates, to properly update the offsets.
-    void setRefreshRateFps(float fps) override { mRefreshRateFps = fps; }
+    void setRefreshRateFps(Fps fps) override { mRefreshRateFps = fps; }
 
     // Returns current offsets in human friendly format.
     void dump(std::string& result) const override;
 
 protected:
-    void initializeOffsets(const std::vector<float>& refreshRates);
+    void initializeOffsets(const std::vector<Fps>& refreshRates);
     virtual VsyncConfiguration::VsyncConfigSet constructOffsets(nsecs_t vsyncDuration) const = 0;
 
-    std::unordered_map<float, VsyncConfigSet> mOffsets;
-    std::atomic<float> mRefreshRateFps;
+    std::unordered_map<Fps, VsyncConfigSet, std::hash<Fps>, Fps::EqualsInBuckets> mOffsets;
+    std::atomic<Fps> mRefreshRateFps;
 };
 
 /*
@@ -86,10 +89,9 @@
 
 protected:
     // Used for unit tests
-    PhaseOffsets(const std::vector<float>& refreshRates, float currentFps,
-                 nsecs_t vsyncPhaseOffsetNs, nsecs_t sfVSyncPhaseOffsetNs,
-                 std::optional<nsecs_t> earlySfOffsetNs, std::optional<nsecs_t> earlyGpuSfOffsetNs,
-                 std::optional<nsecs_t> earlyAppOffsetNs,
+    PhaseOffsets(const std::vector<Fps>& refreshRates, Fps currentFps, nsecs_t vsyncPhaseOffsetNs,
+                 nsecs_t sfVSyncPhaseOffsetNs, std::optional<nsecs_t> earlySfOffsetNs,
+                 std::optional<nsecs_t> earlyGpuSfOffsetNs, std::optional<nsecs_t> earlyAppOffsetNs,
                  std::optional<nsecs_t> earlyGpuAppOffsetNs, nsecs_t highFpsVsyncPhaseOffsetNs,
                  nsecs_t highFpsSfVSyncPhaseOffsetNs, std::optional<nsecs_t> highFpsEarlySfOffsetNs,
                  std::optional<nsecs_t> highFpsEarlyGpuSfOffsetNs,
@@ -130,7 +132,7 @@
 
 protected:
     // Used for unit tests
-    WorkDuration(const std::vector<float>& refreshRates, float currentFps, nsecs_t sfDuration,
+    WorkDuration(const std::vector<Fps>& refreshRates, Fps currentFps, nsecs_t sfDuration,
                  nsecs_t appDuration, nsecs_t sfEarlyDuration, nsecs_t appEarlyDuration,
                  nsecs_t sfEarlyGpuDuration, nsecs_t appEarlyGpuDuration);
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 95c9982..39f7391 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -921,10 +921,10 @@
         }
 
         const nsecs_t period = hwConfig->getVsyncPeriod();
-        config.refreshRate = 1e9f / period;
+        config.refreshRate = Fps::fromPeriodNsecs(period).getValue();
 
         const auto vsyncConfigSet =
-                mVsyncConfiguration->getConfigsForRefreshRate(config.refreshRate);
+                mVsyncConfiguration->getConfigsForRefreshRate(Fps(config.refreshRate));
         config.appVsyncOffset = vsyncConfigSet.late.appOffset;
         config.sfVsyncOffset = vsyncConfigSet.late.sfOffset;
         config.configGroup = hwConfig->getConfigGroup();
@@ -1042,7 +1042,7 @@
             return INVALID_OPERATION;
         } else {
             const HwcConfigIndexType config(mode);
-            const float fps = mRefreshRateConfigs->getRefreshRateFromConfigId(config).getFps();
+            const auto fps = mRefreshRateConfigs->getRefreshRateFromConfigId(config).getFps();
             // Keep the old switching type.
             const auto allowGroupSwitching =
                     mRefreshRateConfigs->getCurrentPolicy().allowGroupSwitching;
@@ -1080,7 +1080,7 @@
         mTimeStats->incrementRefreshRateSwitches();
     }
     updatePhaseConfiguration(refreshRate);
-    ATRACE_INT("ActiveConfigFPS", refreshRate.getFps());
+    ATRACE_INT("ActiveConfigFPS", refreshRate.getFps().getValue());
 
     if (mUpcomingActiveConfig.event != Scheduler::ConfigEvent::None) {
         const nsecs_t vsyncPeriod =
@@ -1138,7 +1138,7 @@
     mUpcomingActiveConfig = *desiredActiveConfig;
     const auto displayId = display->getPhysicalId();
 
-    ATRACE_INT("ActiveConfigFPS_HWC", refreshRate.getFps());
+    ATRACE_INT("ActiveConfigFPS_HWC", refreshRate.getFps().getValue());
 
     // TODO(b/142753666) use constrains
     hal::VsyncPeriodChangeConstraints constraints;
@@ -3775,7 +3775,7 @@
     if (what & layer_state_t::eFrameRateChanged) {
         if (ValidateFrameRate(s.frameRate, s.frameRateCompatibility,
                               "SurfaceFlinger::setClientStateLocked") &&
-            layer->setFrameRate(Layer::FrameRate(s.frameRate,
+            layer->setFrameRate(Layer::FrameRate(Fps(s.frameRate),
                                                  Layer::FrameRate::convertCompatibility(
                                                          s.frameRateCompatibility),
                                                  s.shouldBeSeamless))) {
@@ -4708,8 +4708,8 @@
         const auto activeConfig = getHwComposer().getActiveConfig(*displayId);
         std::string fps, xDpi, yDpi;
         if (activeConfig) {
-            fps = base::StringPrintf("%.2f Hz",
-                                     1e9f / getHwComposer().getDisplayVsyncPeriod(*displayId));
+            const auto vsyncPeriod = getHwComposer().getDisplayVsyncPeriod(*displayId);
+            fps = base::StringPrintf("%s", to_string(Fps::fromPeriodNsecs(vsyncPeriod)).c_str());
             xDpi = base::StringPrintf("%.2f", activeConfig->getDpiX());
             yDpi = base::StringPrintf("%.2f", activeConfig->getDpiY());
         } else {
@@ -5965,11 +5965,7 @@
     }
     scheduler::RefreshRateConfigs::Policy currentPolicy = mRefreshRateConfigs->getCurrentPolicy();
 
-    ALOGV("Setting desired display config specs: defaultConfig: %d primaryRange: [%.0f %.0f]"
-          " expandedRange: [%.0f %.0f]",
-          currentPolicy.defaultConfig.value(), currentPolicy.primaryRange.min,
-          currentPolicy.primaryRange.max, currentPolicy.appRequestRange.min,
-          currentPolicy.appRequestRange.max);
+    ALOGV("Setting desired display config specs: %s", currentPolicy.toString().c_str());
 
     // TODO(b/140204874): Leave the event in until we do proper testing with all apps that might
     // be depending in this callback.
@@ -6025,8 +6021,8 @@
             using Policy = scheduler::RefreshRateConfigs::Policy;
             const Policy policy{HwcConfigIndexType(defaultConfig),
                                 allowGroupSwitching,
-                                {primaryRefreshRateMin, primaryRefreshRateMax},
-                                {appRequestRefreshRateMin, appRequestRefreshRateMax}};
+                                {Fps(primaryRefreshRateMin), Fps(primaryRefreshRateMax)},
+                                {Fps(appRequestRefreshRateMin), Fps(appRequestRefreshRateMax)}};
             constexpr bool kOverridePolicy = false;
 
             return setDesiredDisplayConfigSpecsInternal(display, policy, kOverridePolicy);
@@ -6058,10 +6054,10 @@
                 mRefreshRateConfigs->getDisplayManagerPolicy();
         *outDefaultConfig = policy.defaultConfig.value();
         *outAllowGroupSwitching = policy.allowGroupSwitching;
-        *outPrimaryRefreshRateMin = policy.primaryRange.min;
-        *outPrimaryRefreshRateMax = policy.primaryRange.max;
-        *outAppRequestRefreshRateMin = policy.appRequestRange.min;
-        *outAppRequestRefreshRateMax = policy.appRequestRange.max;
+        *outPrimaryRefreshRateMin = policy.primaryRange.min.getValue();
+        *outPrimaryRefreshRateMax = policy.primaryRange.max.getValue();
+        *outAppRequestRefreshRateMin = policy.appRequestRange.min.getValue();
+        *outAppRequestRefreshRateMax = policy.appRequestRange.max.getValue();
         return NO_ERROR;
     } else if (display->isVirtual()) {
         return INVALID_OPERATION;
@@ -6070,10 +6066,10 @@
         *outDefaultConfig = getHwComposer().getActiveConfigIndex(displayId);
         *outAllowGroupSwitching = false;
         auto vsyncPeriod = getHwComposer().getActiveConfig(displayId)->getVsyncPeriod();
-        *outPrimaryRefreshRateMin = 1e9f / vsyncPeriod;
-        *outPrimaryRefreshRateMax = 1e9f / vsyncPeriod;
-        *outAppRequestRefreshRateMin = 1e9f / vsyncPeriod;
-        *outAppRequestRefreshRateMax = 1e9f / vsyncPeriod;
+        *outPrimaryRefreshRateMin = Fps::fromPeriodNsecs(vsyncPeriod).getValue();
+        *outPrimaryRefreshRateMax = Fps::fromPeriodNsecs(vsyncPeriod).getValue();
+        *outAppRequestRefreshRateMin = Fps::fromPeriodNsecs(vsyncPeriod).getValue();
+        *outAppRequestRefreshRateMax = Fps::fromPeriodNsecs(vsyncPeriod).getValue();
         return NO_ERROR;
     }
 }
@@ -6169,7 +6165,7 @@
                 return BAD_VALUE;
             }
             if (layer->setFrameRate(
-                        Layer::FrameRate(frameRate,
+                        Layer::FrameRate(Fps{frameRate},
                                          Layer::FrameRate::convertCompatibility(compatibility),
                                          shouldBeSeamless))) {
                 setTransactionFlags(eTraversalNeeded);
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 871222c..a00e959 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -44,6 +44,7 @@
         "DisplayDevice_GetBestColorModeTest.cpp",
         "DisplayDevice_SetProjectionTest.cpp",
         "EventThreadTest.cpp",
+        "FpsTest.cpp",
         "FrameTimelineTest.cpp",
         "HWComposerTest.cpp",
         "OneShotTimerTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/FakeVsyncConfiguration.h b/services/surfaceflinger/tests/unittests/FakeVsyncConfiguration.h
index 4cd1e0a..36e24d2 100644
--- a/services/surfaceflinger/tests/unittests/FakeVsyncConfiguration.h
+++ b/services/surfaceflinger/tests/unittests/FakeVsyncConfiguration.h
@@ -26,7 +26,7 @@
     static constexpr nsecs_t FAKE_PHASE_OFFSET_NS = 0;
     static constexpr auto FAKE_DURATION_OFFSET_NS = std::chrono::nanoseconds(0);
 
-    VsyncConfigSet getConfigsForRefreshRate(float) const override { return getCurrentConfigs(); }
+    VsyncConfigSet getConfigsForRefreshRate(Fps) const override { return getCurrentConfigs(); }
 
     VsyncConfigSet getCurrentConfigs() const override {
         return {{FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS, FAKE_DURATION_OFFSET_NS,
@@ -37,7 +37,7 @@
                  FAKE_DURATION_OFFSET_NS}};
     }
 
-    void setRefreshRateFps(float) override {}
+    void setRefreshRateFps(Fps) override {}
     void dump(std::string&) const override {}
 };
 
diff --git a/services/surfaceflinger/tests/unittests/FpsTest.cpp b/services/surfaceflinger/tests/unittests/FpsTest.cpp
new file mode 100644
index 0000000..db732cf
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/FpsTest.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Fps.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace android {
+
+TEST(FpsTest, construct) {
+    Fps fpsDefault;
+    EXPECT_FALSE(fpsDefault.isValid());
+
+    Fps fps1(60.0f);
+    EXPECT_TRUE(fps1.isValid());
+    Fps fps2 = Fps::fromPeriodNsecs(static_cast<nsecs_t>(1e9f / 60.0f));
+    EXPECT_TRUE(fps2.isValid());
+    EXPECT_TRUE(fps1.equalsWithMargin(fps2));
+}
+
+TEST(FpsTest, compare) {
+    constexpr float kEpsilon = 1e-4f;
+    const Fps::EqualsInBuckets equalsInBuckets;
+    const Fps::EqualsWithMargin equalsWithMargin;
+
+    EXPECT_TRUE(Fps(60.0f).equalsWithMargin(Fps(60.f)));
+    EXPECT_TRUE(Fps(60.0f).equalsWithMargin(Fps(60.f - kEpsilon)));
+    EXPECT_TRUE(Fps(60.0f).equalsWithMargin(Fps(60.f + kEpsilon)));
+
+    EXPECT_TRUE(equalsInBuckets(Fps(60.0f), Fps(60.0f)));
+    EXPECT_TRUE(equalsInBuckets(Fps(60.0f), Fps(60.0f - kEpsilon)));
+    EXPECT_TRUE(equalsInBuckets(Fps(60.0f), Fps(60.0f + kEpsilon)));
+
+    EXPECT_TRUE(equalsWithMargin(Fps(60.0f), Fps(60.0f)));
+    EXPECT_TRUE(equalsWithMargin(Fps(60.0f), Fps(60.0f - kEpsilon)));
+    EXPECT_TRUE(equalsWithMargin(Fps(60.0f), Fps(60.0f + kEpsilon)));
+
+    EXPECT_TRUE(Fps(60.0f).lessThanOrEqualWithMargin(Fps(60.f + kEpsilon)));
+    EXPECT_TRUE(Fps(60.0f).lessThanOrEqualWithMargin(Fps(60.f)));
+    EXPECT_TRUE(Fps(60.0f).lessThanOrEqualWithMargin(Fps(60.f - kEpsilon)));
+
+    EXPECT_TRUE(Fps(60.0f).greaterThanOrEqualWithMargin(Fps(60.f + kEpsilon)));
+    EXPECT_TRUE(Fps(60.0f).greaterThanOrEqualWithMargin(Fps(60.f)));
+    EXPECT_TRUE(Fps(60.0f).greaterThanOrEqualWithMargin(Fps(60.f - kEpsilon)));
+
+    // Fps with difference of 1 should be different
+    EXPECT_FALSE(Fps(60.0f).equalsWithMargin(Fps(61.f)));
+    EXPECT_TRUE(Fps(60.0f).lessThanWithMargin(Fps(61.f)));
+    EXPECT_TRUE(Fps(60.0f).greaterThanWithMargin(Fps(59.f)));
+
+    // These are common refresh rates which should be different.
+    EXPECT_FALSE(Fps(60.0f).equalsWithMargin(Fps(59.94f)));
+    EXPECT_TRUE(Fps(60.0f).greaterThanWithMargin(Fps(59.94f)));
+    EXPECT_FALSE(equalsInBuckets(Fps(60.0f), Fps(59.94f)));
+    EXPECT_FALSE(equalsWithMargin(Fps(60.0f), Fps(59.94f)));
+    EXPECT_NE(std::hash<Fps>()(Fps(60.0f)), std::hash<Fps>()(Fps(59.94f)));
+
+    EXPECT_FALSE(Fps(30.0f).equalsWithMargin(Fps(29.97f)));
+    EXPECT_TRUE(Fps(30.0f).greaterThanWithMargin(Fps(29.97f)));
+    EXPECT_FALSE(equalsInBuckets(Fps(30.0f), Fps(29.97f)));
+    EXPECT_FALSE(equalsWithMargin(Fps(30.0f), Fps(29.97f)));
+    EXPECT_NE(std::hash<Fps>()(Fps(30.0f)), std::hash<Fps>()(Fps(29.97f)));
+}
+
+TEST(FpsTest, getIntValue) {
+    EXPECT_EQ(30, Fps(30.1f).getIntValue());
+    EXPECT_EQ(31, Fps(30.9f).getIntValue());
+    EXPECT_EQ(31, Fps(30.5f).getIntValue());
+}
+
+TEST(FpsTest, equalsInBucketsImpliesEqualHashes) {
+    constexpr float kStep = 1e-4f;
+    const Fps::EqualsInBuckets equals;
+    for (float fps = 30.0f; fps < 31.0f; fps += kStep) {
+        const Fps left(fps);
+        const Fps right(fps + kStep);
+        if (equals(left, right)) {
+            ASSERT_EQ(std::hash<Fps>()(left), std::hash<Fps>()(right))
+                    << "left= " << left << " right=" << right;
+        }
+    }
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index fbb4637..cbc1e02 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -32,7 +32,9 @@
 using testing::_;
 using testing::Return;
 
-namespace android::scheduler {
+namespace android {
+
+namespace scheduler {
 
 class LayerHistoryTest : public testing::Test {
 protected:
@@ -43,11 +45,11 @@
     static constexpr auto REFRESH_RATE_AVERAGE_HISTORY_DURATION =
             LayerInfo::RefreshRateHistory::HISTORY_DURATION;
 
-    static constexpr float LO_FPS = 30.f;
-    static constexpr auto LO_FPS_PERIOD = static_cast<nsecs_t>(1e9f / LO_FPS);
+    static constexpr Fps LO_FPS{30.f};
+    static constexpr auto LO_FPS_PERIOD = LO_FPS.getPeriodNsecs();
 
-    static constexpr float HI_FPS = 90.f;
-    static constexpr auto HI_FPS_PERIOD = static_cast<nsecs_t>(1e9f / HI_FPS);
+    static constexpr Fps HI_FPS{90.f};
+    static constexpr auto HI_FPS_PERIOD = HI_FPS.getPeriodNsecs();
 
     LayerHistoryTest() { mFlinger.resetScheduler(mScheduler); }
 
@@ -88,20 +90,19 @@
         return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger(), std::move(name)));
     }
 
-    void recordFramesAndExpect(const sp<mock::MockLayer>& layer, nsecs_t& time, float frameRate,
-                               float desiredRefreshRate, int numFrames) {
-        const nsecs_t framePeriod = static_cast<nsecs_t>(1e9f / frameRate);
+    void recordFramesAndExpect(const sp<mock::MockLayer>& layer, nsecs_t& time, Fps frameRate,
+                               Fps desiredRefreshRate, int numFrames) {
         LayerHistory::Summary summary;
         for (int i = 0; i < numFrames; i++) {
             history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-            time += framePeriod;
+            time += frameRate.getPeriodNsecs();
 
             summary = history().summarize(time);
         }
 
         ASSERT_EQ(1, summary.size());
         ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-        ASSERT_FLOAT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate)
+        ASSERT_TRUE(desiredRefreshRate.equalsWithMargin(summary[0].desiredRefreshRate))
                 << "Frame rate is " << frameRate;
     }
 
@@ -196,7 +197,7 @@
 
     ASSERT_EQ(1, history().summarize(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_TRUE(LO_FPS.equalsWithMargin(history().summarize(time)[0].desiredRefreshRate));
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 }
@@ -289,7 +290,7 @@
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameRateForLayerTree())
             .WillRepeatedly(
-                    Return(Layer::FrameRate(73.4f, Layer::FrameRateCompatibility::Default)));
+                    Return(Layer::FrameRate(Fps(73.4f), Layer::FrameRateCompatibility::Default)));
 
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
@@ -302,7 +303,7 @@
 
     ASSERT_EQ(1, history().summarize(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, history().summarize(time)[0].vote);
-    EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_TRUE(Fps(73.4f).equalsWithMargin(history().summarize(time)[0].desiredRefreshRate));
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -311,7 +312,7 @@
     time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
     ASSERT_EQ(1, history().summarize(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, history().summarize(time)[0].vote);
-    EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_TRUE(Fps(73.4f).equalsWithMargin(history().summarize(time)[0].desiredRefreshRate));
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
 }
@@ -321,7 +322,7 @@
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameRateForLayerTree())
             .WillRepeatedly(Return(
-                    Layer::FrameRate(73.4f, Layer::FrameRateCompatibility::ExactOrMultiple)));
+                    Layer::FrameRate(Fps(73.4f), Layer::FrameRateCompatibility::ExactOrMultiple)));
 
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
@@ -335,7 +336,7 @@
     ASSERT_EQ(1, history().summarize(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
               history().summarize(time)[0].vote);
-    EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_TRUE(Fps(73.4f).equalsWithMargin(history().summarize(time)[0].desiredRefreshRate));
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -345,7 +346,7 @@
     ASSERT_EQ(1, history().summarize(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
               history().summarize(time)[0].vote);
-    EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_TRUE(Fps(73.4f).equalsWithMargin(history().summarize(time)[0].desiredRefreshRate));
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
 }
@@ -397,7 +398,8 @@
     ASSERT_EQ(2, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
     ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
-    EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
+    EXPECT_TRUE(HI_FPS.equalsWithMargin(history().summarize(time)[1].desiredRefreshRate));
+
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -411,7 +413,7 @@
 
     ASSERT_EQ(1, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -430,7 +432,7 @@
 
     ASSERT_EQ(2, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
     EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[1].vote);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
@@ -440,9 +442,9 @@
     summary = history().summarize(time);
     ASSERT_EQ(2, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
-    EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
+    EXPECT_TRUE(HI_FPS.equalsWithMargin(summary[1].desiredRefreshRate));
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
 
@@ -452,9 +454,9 @@
     ASSERT_EQ(2, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
-    EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
+    EXPECT_TRUE(HI_FPS.equalsWithMargin(summary[1].desiredRefreshRate));
     EXPECT_EQ(2, layerCount());
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
@@ -469,7 +471,7 @@
 
     ASSERT_EQ(1, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+    EXPECT_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -490,7 +492,7 @@
 
     ASSERT_EQ(1, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_FLOAT_EQ(HI_FPS, summary[0].desiredRefreshRate);
+    EXPECT_TRUE(HI_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
@@ -567,12 +569,12 @@
     EXPECT_CALL(*explicitVisiblelayer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*explicitVisiblelayer, getFrameRateForLayerTree())
             .WillRepeatedly(Return(
-                    Layer::FrameRate(60.0f, Layer::FrameRateCompatibility::ExactOrMultiple)));
+                    Layer::FrameRate(Fps(60.0f), Layer::FrameRateCompatibility::ExactOrMultiple)));
 
     EXPECT_CALL(*explicitInvisiblelayer, isVisible()).WillRepeatedly(Return(false));
     EXPECT_CALL(*explicitInvisiblelayer, getFrameRateForLayerTree())
             .WillRepeatedly(Return(
-                    Layer::FrameRate(90.0f, Layer::FrameRateCompatibility::ExactOrMultiple)));
+                    Layer::FrameRate(Fps(90.0f), Layer::FrameRateCompatibility::ExactOrMultiple)));
 
     nsecs_t time = systemTime();
 
@@ -585,7 +587,7 @@
     ASSERT_EQ(1, history().summarize(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
               history().summarize(time)[0].vote);
-    EXPECT_FLOAT_EQ(60.0f, history().summarize(time)[0].desiredRefreshRate);
+    EXPECT_TRUE(Fps(60.0f).equalsWithMargin(history().summarize(time)[0].desiredRefreshRate));
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
 }
@@ -643,7 +645,7 @@
 
     nsecs_t time = systemTime();
     for (float fps = 54.0f; fps < 65.0f; fps += 0.1f) {
-        recordFramesAndExpect(layer, time, fps, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+        recordFramesAndExpect(layer, time, Fps(fps), Fps(60.0f), PRESENT_TIME_HISTORY_SIZE);
     }
 }
 
@@ -653,13 +655,13 @@
     EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
 
     nsecs_t time = systemTime();
-    recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, Fps(60.0f), Fps(60.0f), PRESENT_TIME_HISTORY_SIZE);
 
-    recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 30.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 30.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 60.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, Fps(60.0f), Fps(60.0f), PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, Fps(30.0f), Fps(60.0f), PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, Fps(30.0f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, Fps(60.0f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, Fps(60.0f), Fps(60.0f), PRESENT_TIME_HISTORY_SIZE);
 }
 
 TEST_F(LayerHistoryTest, heuristicLayerNotOscillating) {
@@ -669,11 +671,11 @@
 
     nsecs_t time = systemTime();
 
-    recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 26.90f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 26.00f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 26.90f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, Fps(27.10f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, Fps(26.90f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, Fps(26.00f), Fps(24.0f), PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, Fps(26.90f), Fps(24.0f), PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, Fps(27.10f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
 }
 
 class LayerHistoryTestParameterized : public LayerHistoryTest,
@@ -724,7 +726,7 @@
 
             bool max = false;
             bool min = false;
-            float heuristic = 0;
+            Fps heuristic{0.0};
             for (const auto& layer : history().summarize(time)) {
                 if (layer.vote == LayerHistory::LayerVoteType::Heuristic) {
                     heuristic = layer.desiredRefreshRate;
@@ -736,7 +738,7 @@
             }
 
             if (infrequentLayerUpdates > FREQUENT_LAYER_WINDOW_SIZE) {
-                EXPECT_FLOAT_EQ(24.0f, heuristic);
+                EXPECT_TRUE(Fps(24.0f).equalsWithMargin(heuristic));
                 EXPECT_FALSE(max);
                 if (history().summarize(time).size() == 2) {
                     EXPECT_TRUE(min);
@@ -750,4 +752,5 @@
                         ::testing::Values(1s, 2s, 3s, 4s, 5s));
 
 } // namespace
-} // namespace android::scheduler
+} // namespace scheduler
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index f2b7191..83ad737 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -30,6 +30,7 @@
 using testing::_;
 
 namespace android {
+
 namespace scheduler {
 
 namespace hal = android::hardware::graphics::composer::hal;
@@ -43,11 +44,11 @@
     RefreshRateConfigsTest();
     ~RefreshRateConfigsTest();
 
-    float findClosestKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs, float frameRate) {
+    Fps findClosestKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs, Fps frameRate) {
         return refreshRateConfigs.findClosestKnownFrameRate(frameRate);
     }
 
-    std::vector<float> getKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs) {
+    std::vector<Fps> getKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs) {
         return refreshRateConfigs.mKnownFrameRates;
     }
 
@@ -62,29 +63,29 @@
 
     // Test configs
     std::shared_ptr<const HWC2::Display::Config> mConfig60 =
-            createConfig(HWC_CONFIG_ID_60, 0, static_cast<int64_t>(1e9f / 60));
+            createConfig(HWC_CONFIG_ID_60, 0, Fps(60.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig90 =
-            createConfig(HWC_CONFIG_ID_90, 0, static_cast<int64_t>(1e9f / 90));
+            createConfig(HWC_CONFIG_ID_90, 0, Fps(90.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig90DifferentGroup =
-            createConfig(HWC_CONFIG_ID_90, 1, static_cast<int64_t>(1e9f / 90));
+            createConfig(HWC_CONFIG_ID_90, 1, Fps(90.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig90DifferentResolution =
-            createConfig(HWC_CONFIG_ID_90, 0, static_cast<int64_t>(1e9f / 90), 111, 222);
+            createConfig(HWC_CONFIG_ID_90, 0, Fps(90.0f).getPeriodNsecs(), 111, 222);
     std::shared_ptr<const HWC2::Display::Config> mConfig72 =
-            createConfig(HWC_CONFIG_ID_72, 0, static_cast<int64_t>(1e9f / 72));
+            createConfig(HWC_CONFIG_ID_72, 0, Fps(72.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig72DifferentGroup =
-            createConfig(HWC_CONFIG_ID_72, 1, static_cast<int64_t>(1e9f / 72));
+            createConfig(HWC_CONFIG_ID_72, 1, Fps(72.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig120 =
-            createConfig(HWC_CONFIG_ID_120, 0, static_cast<int64_t>(1e9f / 120));
+            createConfig(HWC_CONFIG_ID_120, 0, Fps(120.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig120DifferentGroup =
-            createConfig(HWC_CONFIG_ID_120, 1, static_cast<int64_t>(1e9f / 120));
+            createConfig(HWC_CONFIG_ID_120, 1, Fps(120.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig30 =
-            createConfig(HWC_CONFIG_ID_30, 0, static_cast<int64_t>(1e9f / 30));
+            createConfig(HWC_CONFIG_ID_30, 0, Fps(30.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig30DifferentGroup =
-            createConfig(HWC_CONFIG_ID_30, 1, static_cast<int64_t>(1e9f / 30));
+            createConfig(HWC_CONFIG_ID_30, 1, Fps(30.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig25DifferentGroup =
-            createConfig(HWC_CONFIG_ID_25, 1, static_cast<int64_t>(1e9f / 25));
+            createConfig(HWC_CONFIG_ID_25, 1, Fps(25.0f).getPeriodNsecs());
     std::shared_ptr<const HWC2::Display::Config> mConfig50 =
-            createConfig(HWC_CONFIG_ID_50, 0, static_cast<int64_t>(1e9f / 50));
+            createConfig(HWC_CONFIG_ID_50, 0, Fps(50.0f).getPeriodNsecs());
 
     // Test device configurations
     // The positions of the configs in the arrays below MUST match their IDs. For example,
@@ -124,23 +125,23 @@
              mConfig50};
 
     // Expected RefreshRate objects
-    RefreshRate mExpected60Config = {HWC_CONFIG_ID_60, mConfig60, "60fps", 60,
+    RefreshRate mExpected60Config = {HWC_CONFIG_ID_60, mConfig60, Fps(60),
                                      RefreshRate::ConstructorTag(0)};
     RefreshRate mExpectedAlmost60Config = {HWC_CONFIG_ID_60,
-                                           createConfig(HWC_CONFIG_ID_60, 0, 16666665), "60fps", 60,
+                                           createConfig(HWC_CONFIG_ID_60, 0, 16666665), Fps(60),
                                            RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected90Config = {HWC_CONFIG_ID_90, mConfig90, "90fps", 90,
+    RefreshRate mExpected90Config = {HWC_CONFIG_ID_90, mConfig90, Fps(90),
                                      RefreshRate::ConstructorTag(0)};
     RefreshRate mExpected90DifferentGroupConfig = {HWC_CONFIG_ID_90, mConfig90DifferentGroup,
-                                                   "90fps", 90, RefreshRate::ConstructorTag(0)};
+                                                   Fps(90), RefreshRate::ConstructorTag(0)};
     RefreshRate mExpected90DifferentResolutionConfig = {HWC_CONFIG_ID_90,
-                                                        mConfig90DifferentResolution, "90fps", 90,
+                                                        mConfig90DifferentResolution, Fps(90),
                                                         RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected72Config = {HWC_CONFIG_ID_72, mConfig72, "72fps", 72,
+    RefreshRate mExpected72Config = {HWC_CONFIG_ID_72, mConfig72, Fps(72.0f),
                                      RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected30Config = {HWC_CONFIG_ID_30, mConfig30, "30fps", 30,
+    RefreshRate mExpected30Config = {HWC_CONFIG_ID_30, mConfig30, Fps(30),
                                      RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected120Config = {HWC_CONFIG_ID_120, mConfig120, "120fps", 120,
+    RefreshRate mExpected120Config = {HWC_CONFIG_ID_120, mConfig120, Fps(120),
                                       RefreshRate::ConstructorTag(0)};
 
     Hwc2::mock::Display mDisplay;
@@ -192,8 +193,11 @@
     auto refreshRateConfigs =
             std::make_unique<RefreshRateConfigs>(m60OnlyConfigDevice,
                                                  /*currentConfigId=*/HWC_CONFIG_ID_60);
-    ASSERT_LT(refreshRateConfigs->setDisplayManagerPolicy({HwcConfigIndexType(10), {60, 60}}), 0);
-    ASSERT_LT(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {20, 40}}), 0);
+    ASSERT_LT(refreshRateConfigs->setDisplayManagerPolicy(
+                      {HwcConfigIndexType(10), {Fps(60), Fps(60)}}),
+              0);
+    ASSERT_LT(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(20), Fps(40)}}),
+              0);
 }
 
 TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_storesFullRefreshRateMap) {
@@ -227,7 +231,8 @@
     ASSERT_EQ(mExpected60Config, minRate60);
     ASSERT_EQ(mExpected60Config, performanceRate60);
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {60, 90}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {Fps(60), Fps(90)}}),
+              0);
     refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
 
     const auto& minRate90 = refreshRateConfigs->getMinRefreshRateByPolicy();
@@ -252,7 +257,8 @@
     ASSERT_EQ(mExpected60Config, minRate60);
     ASSERT_EQ(mExpected60Config, performanceRate60);
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {60, 90}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {Fps(60), Fps(90)}}),
+              0);
     refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
 
     const auto& minRate90 = refreshRateConfigs->getMinRefreshRateByPolicy();
@@ -274,7 +280,8 @@
     ASSERT_EQ(mExpected60Config, minRate);
     ASSERT_EQ(mExpected90Config, performanceRate);
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 60}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
+              0);
 
     auto& minRate60 = refreshRateConfigs->getMinRefreshRateByPolicy();
     auto& performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
@@ -297,7 +304,8 @@
         EXPECT_EQ(current.getConfigId(), HWC_CONFIG_ID_90);
     }
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {90, 90}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {Fps(90), Fps(90)}}),
+              0);
     {
         auto& current = refreshRateConfigs->getCurrentRefreshRate();
         EXPECT_EQ(current.getConfigId(), HWC_CONFIG_ID_90);
@@ -315,7 +323,8 @@
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 60}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
+              0);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 }
@@ -338,34 +347,35 @@
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 90.0f;
+    lr.desiredRefreshRate = Fps(90.0f);
     lr.vote = LayerVoteType::Heuristic;
     lr.name = "90Hz Heuristic";
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     lr.name = "60Hz Heuristic";
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 45.0f;
+    lr.desiredRefreshRate = Fps(45.0f);
     lr.name = "45Hz Heuristic";
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 30.0f;
+    lr.desiredRefreshRate = Fps(30.0f);
     lr.name = "30Hz Heuristic";
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 24.0f;
+    lr.desiredRefreshRate = Fps(24.0f);
     lr.name = "24Hz Heuristic";
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr.name = "";
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 60}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
+              0);
 
     lr.vote = LayerVoteType::Min;
     EXPECT_EQ(mExpected60Config,
@@ -375,28 +385,30 @@
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 90.0f;
+    lr.desiredRefreshRate = Fps(90.0f);
     lr.vote = LayerVoteType::Heuristic;
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 45.0f;
+    lr.desiredRefreshRate = Fps(45.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 30.0f;
+    lr.desiredRefreshRate = Fps(30.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 24.0f;
+    lr.desiredRefreshRate = Fps(24.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {90, 90}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
+                      {HWC_CONFIG_ID_90, {Fps(90.0f), Fps(90.0f)}}),
+              0);
 
     lr.vote = LayerVoteType::Min;
     EXPECT_EQ(mExpected90Config,
@@ -406,28 +418,30 @@
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 90.0f;
+    lr.desiredRefreshRate = Fps(90.0f);
     lr.vote = LayerVoteType::Heuristic;
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 45.0f;
+    lr.desiredRefreshRate = Fps(45.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 30.0f;
+    lr.desiredRefreshRate = Fps(30.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 24.0f;
+    lr.desiredRefreshRate = Fps(24.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {0, 120}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
+                      {HWC_CONFIG_ID_60, {Fps(0.0f), Fps(120.0f)}}),
+              0);
     lr.vote = LayerVoteType::Min;
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
@@ -436,24 +450,24 @@
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 90.0f;
+    lr.desiredRefreshRate = Fps(90.0f);
     lr.vote = LayerVoteType::Heuristic;
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 45.0f;
+    lr.desiredRefreshRate = Fps(45.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 30.0f;
+    lr.desiredRefreshRate = Fps(30.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 24.0f;
+    lr.desiredRefreshRate = Fps(24.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 }
@@ -474,24 +488,24 @@
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 90.0f;
+    lr.desiredRefreshRate = Fps(90.0f);
     lr.vote = LayerVoteType::Heuristic;
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 45.0f;
+    lr.desiredRefreshRate = Fps(45.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 30.0f;
+    lr.desiredRefreshRate = Fps(30.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 24.0f;
+    lr.desiredRefreshRate = Fps(24.0f);
     EXPECT_EQ(mExpected72Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 }
@@ -506,23 +520,23 @@
     auto& lr1 = layers[0];
     auto& lr2 = layers[1];
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     lr2.vote = LayerVoteType::Heuristic;
     EXPECT_EQ(mExpected120Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 48.0f;
+    lr2.desiredRefreshRate = Fps(48.0f);
     lr2.vote = LayerVoteType::Heuristic;
     EXPECT_EQ(mExpected72Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 48.0f;
+    lr2.desiredRefreshRate = Fps(48.0f);
     lr2.vote = LayerVoteType::Heuristic;
     EXPECT_EQ(mExpected72Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
@@ -538,82 +552,82 @@
     auto& lr1 = layers[0];
     auto& lr2 = layers[1];
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::ExplicitDefault;
     lr1.name = "24Hz ExplicitDefault";
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
     EXPECT_EQ(mExpected120Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
     EXPECT_EQ(mExpected120Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "60Hz ExplicitDefault";
     EXPECT_EQ(mExpected120Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz Heuristic";
     EXPECT_EQ(mExpected72Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::ExplicitDefault;
     lr1.name = "24Hz ExplicitDefault";
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::Heuristic;
     lr1.name = "24Hz Heuristic";
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
     EXPECT_EQ(mExpected72Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
     EXPECT_EQ(mExpected72Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr1.desiredRefreshRate = 24.0f;
+    lr1.desiredRefreshRate = Fps(24.0f);
     lr1.vote = LayerVoteType::ExplicitDefault;
     lr1.name = "24Hz ExplicitDefault";
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.name = "90Hz ExplicitExactOrMultiple";
     EXPECT_EQ(mExpected90Config,
@@ -636,24 +650,24 @@
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 90.0f;
+    lr.desiredRefreshRate = Fps(90.0f);
     lr.vote = LayerVoteType::Heuristic;
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 45.0f;
+    lr.desiredRefreshRate = Fps(45.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 30.0f;
+    lr.desiredRefreshRate = Fps(30.0f);
     EXPECT_EQ(mExpected30Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 24.0f;
+    lr.desiredRefreshRate = Fps(24.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 }
@@ -676,41 +690,41 @@
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 90.0f;
+    lr.desiredRefreshRate = Fps(90.0f);
     lr.vote = LayerVoteType::Heuristic;
     lr.name = "90Hz Heuristic";
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     lr.name = "60Hz Heuristic";
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
 
-    lr.desiredRefreshRate = 45.0f;
+    lr.desiredRefreshRate = Fps(45.0f);
     lr.name = "45Hz Heuristic";
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
 
-    lr.desiredRefreshRate = 30.0f;
+    lr.desiredRefreshRate = Fps(30.0f);
     lr.name = "30Hz Heuristic";
     EXPECT_EQ(mExpected30Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
 
-    lr.desiredRefreshRate = 24.0f;
+    lr.desiredRefreshRate = Fps(24.0f);
     lr.name = "24Hz Heuristic";
     EXPECT_EQ(mExpected72Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
 
-    lr.desiredRefreshRate = 24.0f;
+    lr.desiredRefreshRate = Fps(24.0f);
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr.name = "24Hz ExplicitExactOrMultiple";
     EXPECT_EQ(mExpected72Config,
@@ -736,39 +750,39 @@
 
     lr1.vote = LayerVoteType::Min;
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 24.0f;
+    lr2.desiredRefreshRate = Fps(24.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::Min;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 24.0f;
+    lr2.desiredRefreshRate = Fps(24.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::Max;
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::Max;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::Heuristic;
-    lr1.desiredRefreshRate = 15.0f;
+    lr1.desiredRefreshRate = Fps(15.0f);
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 45.0f;
+    lr2.desiredRefreshRate = Fps(45.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::Heuristic;
-    lr1.desiredRefreshRate = 30.0f;
+    lr1.desiredRefreshRate = Fps(30.0f);
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 45.0f;
+    lr2.desiredRefreshRate = Fps(45.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 }
@@ -783,7 +797,7 @@
 
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
-        lr.desiredRefreshRate = fps;
+        lr.desiredRefreshRate = Fps(fps);
         const auto& refreshRate =
                 refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
         EXPECT_EQ(mExpected60Config, refreshRate) << fps << "Hz chooses " << refreshRate.getName();
@@ -801,33 +815,33 @@
     auto& lr2 = layers[1];
 
     lr1.vote = LayerVoteType::Heuristic;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::ExplicitDefault;
-    lr1.desiredRefreshRate = 90.0f;
+    lr1.desiredRefreshRate = Fps(90.0f);
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::Heuristic;
-    lr1.desiredRefreshRate = 90.0f;
+    lr1.desiredRefreshRate = Fps(90.0f);
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 }
 
 TEST_F(RefreshRateConfigsTest, testInPolicy) {
-    ASSERT_TRUE(mExpectedAlmost60Config.inPolicy(60.000004f, 60.000004f));
-    ASSERT_TRUE(mExpectedAlmost60Config.inPolicy(59.0f, 60.1f));
-    ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(75.0f, 90.0f));
-    ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(60.0011f, 90.0f));
-    ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(50.0f, 59.998f));
+    ASSERT_TRUE(mExpectedAlmost60Config.inPolicy(Fps(60.000004f), Fps(60.000004f)));
+    ASSERT_TRUE(mExpectedAlmost60Config.inPolicy(Fps(59.0f), Fps(60.1f)));
+    ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(Fps(75.0f), Fps(90.0f)));
+    ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(Fps(60.0011f), Fps(90.0f)));
+    ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(Fps(50.0f), Fps(59.998f)));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_75HzContent) {
@@ -840,7 +854,7 @@
 
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     for (float fps = 75.0f; fps < 100.0f; fps += 0.1f) {
-        lr.desiredRefreshRate = fps;
+        lr.desiredRefreshRate = Fps(fps);
         const auto& refreshRate =
                 refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
         EXPECT_EQ(mExpected90Config, refreshRate) << fps << "Hz chooses " << refreshRate.getName();
@@ -858,25 +872,25 @@
     auto& lr2 = layers[1];
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.name = "90Hz Heuristic";
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::ExplicitDefault;
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.name = "90Hz ExplicitDefault";
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
@@ -884,16 +898,16 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 30.0f;
+    lr1.desiredRefreshRate = Fps(30.0f);
     lr1.name = "30Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.name = "90Hz Heuristic";
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 30.0f;
+    lr1.desiredRefreshRate = Fps(30.0f);
     lr1.name = "30Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
@@ -912,7 +926,7 @@
     auto& lr2 = layers[1];
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::NoVote;
     lr2.name = "NoVote";
@@ -920,7 +934,7 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::NoVote;
     lr2.name = "NoVote";
@@ -928,7 +942,7 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
@@ -936,7 +950,7 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
@@ -945,10 +959,10 @@
 
     // The other layer starts to provide buffers
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 90.0f;
+    lr2.desiredRefreshRate = Fps(90.0f);
     lr2.name = "90Hz Heuristic";
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
@@ -972,40 +986,40 @@
     auto& lr2 = layers[1];
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     lr2.name = "60Hz Heuristic";
     refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
                                            &consideredSignals);
     EXPECT_EQ(true, consideredSignals.touch);
 
     lr1.vote = LayerVoteType::ExplicitDefault;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     lr2.name = "60Hz Heuristic";
     refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
                                            &consideredSignals);
     EXPECT_EQ(false, consideredSignals.touch);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     lr2.name = "60Hz Heuristic";
     refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
                                            &consideredSignals);
     EXPECT_EQ(true, consideredSignals.touch);
 
     lr1.vote = LayerVoteType::ExplicitDefault;
-    lr1.desiredRefreshRate = 60.0f;
+    lr1.desiredRefreshRate = Fps(60.0f);
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 60.0f;
+    lr2.desiredRefreshRate = Fps(60.0f);
     lr2.name = "60Hz Heuristic";
     refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
                                            &consideredSignals);
@@ -1041,7 +1055,7 @@
 
     for (const auto& test : testCases) {
         lr.vote = LayerVoteType::ExplicitDefault;
-        lr.desiredRefreshRate = test.first;
+        lr.desiredRefreshRate = Fps(test.first);
 
         std::stringstream ss;
         ss << "ExplicitDefault " << test.first << " fps";
@@ -1049,7 +1063,7 @@
 
         const auto& refreshRate =
                 refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
-        EXPECT_FLOAT_EQ(refreshRate.getFps(), test.second)
+        EXPECT_TRUE(refreshRate.getFps().equalsWithMargin(Fps(test.second)))
                 << "Expecting " << test.first << "fps => " << test.second << "Hz";
     }
 }
@@ -1061,7 +1075,7 @@
                                                  /*currentConfigId=*/HWC_CONFIG_ID_90);
 
     ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_90, {90.f, 90.f}, {60.f, 90.f}}),
+                      {HWC_CONFIG_ID_90, {Fps(90.f), Fps(90.f)}, {Fps(60.f), Fps(90.f)}}),
               0);
 
     auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
@@ -1069,7 +1083,7 @@
 
     RefreshRateConfigs::GlobalSignals consideredSignals;
     lr.vote = LayerVoteType::ExplicitDefault;
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     lr.name = "60Hz ExplicitDefault";
     lr.focused = true;
     EXPECT_EQ(mExpected60Config,
@@ -1085,14 +1099,14 @@
                                                  /*currentConfigId=*/HWC_CONFIG_ID_60);
 
     ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_60, {60.f, 60.f}, {60.f, 90.f}}),
+                      {HWC_CONFIG_ID_60, {Fps(60.f), Fps(60.f)}, {Fps(60.f), Fps(90.f)}}),
               0);
 
     auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::ExplicitDefault;
-    lr.desiredRefreshRate = 90.0f;
+    lr.desiredRefreshRate = Fps(90.0f);
     lr.name = "90Hz ExplicitDefault";
     lr.focused = true;
     EXPECT_EQ(mExpected90Config,
@@ -1106,7 +1120,7 @@
                                                  /*currentConfigId=*/HWC_CONFIG_ID_90);
 
     ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_90, {90.f, 90.f}, {60.f, 90.f}}),
+                      {HWC_CONFIG_ID_90, {Fps(90.f), Fps(90.f)}, {Fps(60.f), Fps(90.f)}}),
               0);
 
     RefreshRateConfigs::GlobalSignals consideredSignals;
@@ -1119,7 +1133,7 @@
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     lr.name = "60Hz ExplicitExactOrMultiple";
     lr.focused = false;
     EXPECT_EQ(mExpected90Config,
@@ -1130,7 +1144,7 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr.vote = LayerVoteType::ExplicitDefault;
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     lr.name = "60Hz ExplicitDefault";
     lr.focused = false;
     EXPECT_EQ(mExpected90Config,
@@ -1141,7 +1155,7 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr.vote = LayerVoteType::Heuristic;
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     lr.name = "60Hz Heuristic";
     lr.focused = false;
     EXPECT_EQ(mExpected90Config,
@@ -1152,7 +1166,7 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr.vote = LayerVoteType::Max;
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     lr.name = "60Hz Max";
     lr.focused = false;
     EXPECT_EQ(mExpected90Config,
@@ -1163,7 +1177,7 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
     lr.vote = LayerVoteType::Min;
-    lr.desiredRefreshRate = 60.0f;
+    lr.desiredRefreshRate = Fps(60.0f);
     lr.name = "60Hz Min";
     lr.focused = false;
     EXPECT_EQ(mExpected90Config,
@@ -1182,7 +1196,7 @@
     auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
     auto& layer = layers[0];
     layer.vote = LayerVoteType::ExplicitDefault;
-    layer.desiredRefreshRate = 90.0f;
+    layer.desiredRefreshRate = Fps(90.0f);
     layer.seamlessness = Seamlessness::SeamedAndSeamless;
     layer.name = "90Hz ExplicitDefault";
     layer.focused = true;
@@ -1207,7 +1221,7 @@
 
     // Verify that we won't do a seamless switch if we request the same mode as the default
     refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
-    layer.desiredRefreshRate = 60.0f;
+    layer.desiredRefreshRate = Fps(60.0f);
     layer.name = "60Hz ExplicitDefault";
     layer.seamlessness = Seamlessness::OnlySeamless;
     ASSERT_EQ(HWC_CONFIG_ID_90,
@@ -1216,7 +1230,7 @@
 
     // Verify that if the current config is in another group and there are no layers with
     // seamlessness=SeamedAndSeamless we'll go back to the default group.
-    layer.desiredRefreshRate = 60.0f;
+    layer.desiredRefreshRate = Fps(60.0f);
     layer.name = "60Hz ExplicitDefault";
     layer.seamlessness = Seamlessness::Default;
     ASSERT_EQ(HWC_CONFIG_ID_60,
@@ -1231,7 +1245,7 @@
     layers.push_back(LayerRequirement{.weight = 0.5f});
     auto& layer2 = layers[layers.size() - 1];
     layer2.vote = LayerVoteType::ExplicitDefault;
-    layer2.desiredRefreshRate = 90.0f;
+    layer2.desiredRefreshRate = Fps(90.0f);
     layer2.name = "90Hz ExplicitDefault";
     layer2.seamlessness = Seamlessness::SeamedAndSeamless;
     layer2.focused = false;
@@ -1262,7 +1276,7 @@
     auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
     auto& layer = layers[0];
     layer.vote = LayerVoteType::ExplicitExactOrMultiple;
-    layer.desiredRefreshRate = 60.0f;
+    layer.desiredRefreshRate = Fps(60.0f);
     layer.seamlessness = Seamlessness::SeamedAndSeamless;
     layer.name = "60Hz ExplicitExactOrMultiple";
     layer.focused = true;
@@ -1291,13 +1305,13 @@
     auto layers = std::vector<
             LayerRequirement>{LayerRequirement{.name = "60Hz ExplicitDefault",
                                                .vote = LayerVoteType::ExplicitDefault,
-                                               .desiredRefreshRate = 60.0f,
+                                               .desiredRefreshRate = Fps(60.0f),
                                                .seamlessness = Seamlessness::SeamedAndSeamless,
                                                .weight = 0.5f,
                                                .focused = false},
                               LayerRequirement{.name = "25Hz ExplicitExactOrMultiple",
                                                .vote = LayerVoteType::ExplicitExactOrMultiple,
-                                               .desiredRefreshRate = 25.0f,
+                                               .desiredRefreshRate = Fps(25.0f),
                                                .seamlessness = Seamlessness::OnlySeamless,
                                                .weight = 1.0f,
                                                .focused = true}};
@@ -1307,7 +1321,7 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
                       .getConfigId());
 
-    seamedLayer.name = "30Hz ExplicitDefault", seamedLayer.desiredRefreshRate = 30.0f;
+    seamedLayer.name = "30Hz ExplicitDefault", seamedLayer.desiredRefreshRate = Fps(30.0f);
     refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_30);
 
     ASSERT_EQ(HWC_CONFIG_ID_25,
@@ -1325,7 +1339,7 @@
 
     // Return the config ID from calling getBestRefreshRate() for a single layer with the
     // given voteType and fps.
-    auto getFrameRate = [&](LayerVoteType voteType, float fps, bool touchActive = false,
+    auto getFrameRate = [&](LayerVoteType voteType, Fps fps, bool touchActive = false,
                             bool focused = true) -> HwcConfigIndexType {
         layers[0].vote = voteType;
         layers[0].desiredRefreshRate = fps;
@@ -1335,43 +1349,44 @@
     };
 
     ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_60, {30.f, 60.f}, {30.f, 90.f}}),
+                      {HWC_CONFIG_ID_60, {Fps(30.f), Fps(60.f)}, {Fps(30.f), Fps(90.f)}}),
               0);
     EXPECT_EQ(HWC_CONFIG_ID_60,
               refreshRateConfigs->getBestRefreshRate({}, {.touch = false, .idle = false})
                       .getConfigId());
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::NoVote, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_30, getFrameRate(LayerVoteType::Min, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Heuristic, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_90, getFrameRate(LayerVoteType::ExplicitDefault, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90.f));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::NoVote, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_30, getFrameRate(LayerVoteType::Min, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Heuristic, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_90, getFrameRate(LayerVoteType::ExplicitDefault, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, Fps(90.f)));
 
     // Layers not focused are not allowed to override primary config
     EXPECT_EQ(HWC_CONFIG_ID_60,
-              getFrameRate(LayerVoteType::ExplicitDefault, 90.f, /*touch=*/false,
+              getFrameRate(LayerVoteType::ExplicitDefault, Fps(90.f), /*touch=*/false,
                            /*focused=*/false));
     EXPECT_EQ(HWC_CONFIG_ID_60,
-              getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90.f, /*touch=*/false,
+              getFrameRate(LayerVoteType::ExplicitExactOrMultiple, Fps(90.f), /*touch=*/false,
                            /*focused=*/false));
 
     // Touch boost should be restricted to the primary range.
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, 90.f, /*touch=*/true));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, Fps(90.f), /*touch=*/true));
     // When we're higher than the primary range max due to a layer frame rate setting, touch boost
     // shouldn't drag us back down to the primary range max.
-    EXPECT_EQ(HWC_CONFIG_ID_90, getFrameRate(LayerVoteType::ExplicitDefault, 90.f, /*touch=*/true));
+    EXPECT_EQ(HWC_CONFIG_ID_90,
+              getFrameRate(LayerVoteType::ExplicitDefault, Fps(90.f), /*touch=*/true));
     EXPECT_EQ(HWC_CONFIG_ID_60,
-              getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90.f, /*touch=*/true));
+              getFrameRate(LayerVoteType::ExplicitExactOrMultiple, Fps(90.f), /*touch=*/true));
 
     ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_60, {60.f, 60.f}, {60.f, 60.f}}),
+                      {HWC_CONFIG_ID_60, {Fps(60.f), Fps(60.f)}, {Fps(60.f), Fps(60.f)}}),
               0);
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::NoVote, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Min, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Heuristic, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitDefault, 90.f));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90.f));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::NoVote, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Min, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Heuristic, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitDefault, Fps(90.f)));
+    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, Fps(90.f)));
 }
 
 TEST_F(RefreshRateConfigsTest, idle) {
@@ -1385,7 +1400,7 @@
     const auto getIdleFrameRate = [&](LayerVoteType voteType,
                                       bool touchActive) -> HwcConfigIndexType {
         layers[0].vote = voteType;
-        layers[0].desiredRefreshRate = 90.f;
+        layers[0].desiredRefreshRate = Fps(90.f);
         RefreshRateConfigs::GlobalSignals consideredSignals;
         const auto configId =
                 refreshRateConfigs
@@ -1398,7 +1413,7 @@
     };
 
     ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_60, {60.f, 90.f}, {60.f, 90.f}}),
+                      {HWC_CONFIG_ID_60, {Fps(60.f), Fps(90.f)}, {Fps(60.f), Fps(90.f)}}),
               0);
 
     // Idle should be lower priority than touch boost.
@@ -1439,22 +1454,22 @@
                                                  /*currentConfigId=*/HWC_CONFIG_ID_60);
 
     for (float fps = 1.0f; fps <= 120.0f; fps += 0.1f) {
-        const auto knownFrameRate = findClosestKnownFrameRate(*refreshRateConfigs, fps);
-        float expectedFrameRate;
+        const auto knownFrameRate = findClosestKnownFrameRate(*refreshRateConfigs, Fps(fps));
+        Fps expectedFrameRate;
         if (fps < 26.91f) {
-            expectedFrameRate = 24.0f;
+            expectedFrameRate = Fps(24.0f);
         } else if (fps < 37.51f) {
-            expectedFrameRate = 30.0f;
+            expectedFrameRate = Fps(30.0f);
         } else if (fps < 52.51f) {
-            expectedFrameRate = 45.0f;
+            expectedFrameRate = Fps(45.0f);
         } else if (fps < 66.01f) {
-            expectedFrameRate = 60.0f;
+            expectedFrameRate = Fps(60.0f);
         } else if (fps < 81.01f) {
-            expectedFrameRate = 72.0f;
+            expectedFrameRate = Fps(72.0f);
         } else {
-            expectedFrameRate = 90.0f;
+            expectedFrameRate = Fps(90.0f);
         }
-        EXPECT_FLOAT_EQ(expectedFrameRate, knownFrameRate)
+        EXPECT_TRUE(expectedFrameRate.equalsWithMargin(knownFrameRate))
                 << "findClosestKnownFrameRate(" << fps << ") = " << knownFrameRate;
     }
 }
@@ -1465,26 +1480,27 @@
                                                  /*currentConfigId=*/HWC_CONFIG_ID_60);
 
     struct ExpectedRate {
-        float rate;
+        Fps rate;
         const RefreshRate& expected;
     };
 
     /* clang-format off */
     std::vector<ExpectedRate> knownFrameRatesExpectations = {
-        {24.0f, mExpected60Config},
-        {30.0f, mExpected60Config},
-        {45.0f, mExpected90Config},
-        {60.0f, mExpected60Config},
-        {72.0f, mExpected90Config},
-        {90.0f, mExpected90Config},
+        {Fps(24.0f), mExpected60Config},
+        {Fps(30.0f), mExpected60Config},
+        {Fps(45.0f), mExpected90Config},
+        {Fps(60.0f), mExpected60Config},
+        {Fps(72.0f), mExpected90Config},
+        {Fps(90.0f), mExpected90Config},
     };
     /* clang-format on */
 
     // Make sure the test tests all the known frame rate
     const auto knownFrameRateList = getKnownFrameRate(*refreshRateConfigs);
-    const auto equal = std::equal(knownFrameRateList.begin(), knownFrameRateList.end(),
-                                  knownFrameRatesExpectations.begin(),
-                                  [](float a, const ExpectedRate& b) { return a == b.rate; });
+    const auto equal =
+            std::equal(knownFrameRateList.begin(), knownFrameRateList.end(),
+                       knownFrameRatesExpectations.begin(),
+                       [](Fps a, const ExpectedRate& b) { return a.equalsWithMargin(b.rate); });
     EXPECT_TRUE(equal);
 
     auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
@@ -1514,15 +1530,18 @@
     EXPECT_EQ(KernelIdleTimerAction::TurnOn, refreshRateConfigs->getIdleTimerAction());
 
     // SetPolicy(60, 90), current 60Hz => TurnOn.
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 90}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(90)}}),
+              0);
     EXPECT_EQ(KernelIdleTimerAction::TurnOn, refreshRateConfigs->getIdleTimerAction());
 
     // SetPolicy(60, 60), current 60Hz => NoChange, avoid extra calls.
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 60}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
+              0);
     EXPECT_EQ(KernelIdleTimerAction::NoChange, refreshRateConfigs->getIdleTimerAction());
 
     // SetPolicy(90, 90), current 90Hz => TurnOff.
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {90, 90}}), 0);
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {Fps(90), Fps(90)}}),
+              0);
     EXPECT_EQ(KernelIdleTimerAction::TurnOff, refreshRateConfigs->getIdleTimerAction());
 }
 
@@ -1554,7 +1573,7 @@
     EXPECT_EQ(4, refreshRateConfigs->getRefreshRateDividerForUid(uid));
 
     refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
-    refreshRateConfigs->setPreferredRefreshRateForUid({uid, 22.5});
+    refreshRateConfigs->setPreferredRefreshRateForUid({uid, 22.5f});
     EXPECT_EQ(4, refreshRateConfigs->getRefreshRateDividerForUid(uid));
     refreshRateConfigs->setPreferredRefreshRateForUid({uid, 22.6f});
     EXPECT_EQ(4, refreshRateConfigs->getRefreshRateDividerForUid(uid));
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
index 5278641..c47b141 100644
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
@@ -108,11 +108,12 @@
  */
 class SetFrameRateTest : public ::testing::TestWithParam<std::shared_ptr<LayerFactory>> {
 protected:
-    const FrameRate FRAME_RATE_VOTE1 = FrameRate(67.f, FrameRateCompatibility::Default);
-    const FrameRate FRAME_RATE_VOTE2 = FrameRate(14.f, FrameRateCompatibility::ExactOrMultiple);
-    const FrameRate FRAME_RATE_VOTE3 = FrameRate(99.f, FrameRateCompatibility::NoVote);
-    const FrameRate FRAME_RATE_TREE = FrameRate(0, FrameRateCompatibility::NoVote);
-    const FrameRate FRAME_RATE_NO_VOTE = FrameRate(0, FrameRateCompatibility::Default);
+    const FrameRate FRAME_RATE_VOTE1 = FrameRate(Fps(67.f), FrameRateCompatibility::Default);
+    const FrameRate FRAME_RATE_VOTE2 =
+            FrameRate(Fps(14.f), FrameRateCompatibility::ExactOrMultiple);
+    const FrameRate FRAME_RATE_VOTE3 = FrameRate(Fps(99.f), FrameRateCompatibility::NoVote);
+    const FrameRate FRAME_RATE_TREE = FrameRate(Fps(0.f), FrameRateCompatibility::NoVote);
+    const FrameRate FRAME_RATE_NO_VOTE = FrameRate(Fps(0.f), FrameRateCompatibility::Default);
 
     SetFrameRateTest();
 
diff --git a/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp b/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp
index 72ee6db..2a35f69 100644
--- a/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp
@@ -29,17 +29,18 @@
 
 class TestableWorkDuration : public impl::WorkDuration {
 public:
-    TestableWorkDuration(float currentFps, nsecs_t sfDuration, nsecs_t appDuration,
+    TestableWorkDuration(Fps currentFps, nsecs_t sfDuration, nsecs_t appDuration,
                          nsecs_t sfEarlyDuration, nsecs_t appEarlyDuration,
                          nsecs_t sfEarlyGlDuration, nsecs_t appEarlyGlDuration)
-          : impl::WorkDuration({60.0f, 90.0f}, currentFps, sfDuration, appDuration, sfEarlyDuration,
-                               appEarlyDuration, sfEarlyGlDuration, appEarlyGlDuration) {}
+          : impl::WorkDuration({Fps(60.0f), Fps(90.0f)}, currentFps, sfDuration, appDuration,
+                               sfEarlyDuration, appEarlyDuration, sfEarlyGlDuration,
+                               appEarlyGlDuration) {}
 };
 
 class WorkDurationTest : public testing::Test {
 protected:
     WorkDurationTest()
-          : mWorkDuration(60.0f, 10'500'000, 20'500'000, 16'000'000, 16'500'000, 13'500'000,
+          : mWorkDuration(Fps(60.0f), 10'500'000, 20'500'000, 16'000'000, 16'500'000, 13'500'000,
                           21'000'000) {}
 
     ~WorkDurationTest() = default;
@@ -51,9 +52,9 @@
  * Test cases
  */
 TEST_F(WorkDurationTest, getConfigsForRefreshRate_60Hz) {
-    mWorkDuration.setRefreshRateFps(60.0f);
+    mWorkDuration.setRefreshRateFps(Fps(60.0f));
     auto currentOffsets = mWorkDuration.getCurrentConfigs();
-    auto offsets = mWorkDuration.getConfigsForRefreshRate(60.0f);
+    auto offsets = mWorkDuration.getConfigsForRefreshRate(Fps(60.0f));
 
     EXPECT_EQ(currentOffsets, offsets);
     EXPECT_EQ(offsets.late.sfOffset, 6'166'667);
@@ -76,9 +77,9 @@
 }
 
 TEST_F(WorkDurationTest, getConfigsForRefreshRate_90Hz) {
-    mWorkDuration.setRefreshRateFps(90.0f);
+    mWorkDuration.setRefreshRateFps(Fps(90.0f));
     auto currentOffsets = mWorkDuration.getCurrentConfigs();
-    auto offsets = mWorkDuration.getConfigsForRefreshRate(90.0f);
+    auto offsets = mWorkDuration.getConfigsForRefreshRate(Fps(90.0f));
 
     EXPECT_EQ(currentOffsets, offsets);
     EXPECT_EQ(offsets.late.sfOffset, 611'111);
@@ -101,7 +102,7 @@
 }
 
 TEST_F(WorkDurationTest, getConfigsForRefreshRate_DefaultOffsets) {
-    TestableWorkDuration phaseOffsetsWithDefaultValues(60.0f, -1, -1, -1, -1, -1, -1);
+    TestableWorkDuration phaseOffsetsWithDefaultValues(Fps(60.0f), -1, -1, -1, -1, -1, -1);
 
     auto validateOffsets = [](const auto& offsets, std::chrono::nanoseconds vsyncPeriod) {
         EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
@@ -123,21 +124,20 @@
         EXPECT_EQ(offsets.earlyGpu.appWorkDuration, vsyncPeriod);
     };
 
-    const auto testForRefreshRate = [&](float refreshRate) {
+    const auto testForRefreshRate = [&](Fps refreshRate) {
         phaseOffsetsWithDefaultValues.setRefreshRateFps(refreshRate);
         auto currentOffsets = phaseOffsetsWithDefaultValues.getCurrentConfigs();
         auto offsets = phaseOffsetsWithDefaultValues.getConfigsForRefreshRate(refreshRate);
         EXPECT_EQ(currentOffsets, offsets);
-        validateOffsets(offsets,
-                        std::chrono::nanoseconds(static_cast<nsecs_t>(1e9f / refreshRate)));
+        validateOffsets(offsets, std::chrono::nanoseconds(refreshRate.getPeriodNsecs()));
     };
 
-    testForRefreshRate(90.0f);
-    testForRefreshRate(60.0f);
+    testForRefreshRate(Fps(90.0f));
+    testForRefreshRate(Fps(60.0f));
 }
 
 TEST_F(WorkDurationTest, getConfigsForRefreshRate_unknownRefreshRate) {
-    auto offsets = mWorkDuration.getConfigsForRefreshRate(14.7f);
+    auto offsets = mWorkDuration.getConfigsForRefreshRate(Fps(14.7f));
 
     EXPECT_EQ(offsets.late.sfOffset, 57'527'208);
     EXPECT_EQ(offsets.late.appOffset, 37'027'208);
@@ -171,9 +171,9 @@
                          std::optional<nsecs_t> highFpsEarlyAppOffsetNs,
                          std::optional<nsecs_t> highFpsEarlyGpuAppOffsetNs,
                          nsecs_t thresholdForNextVsync)
-          : impl::PhaseOffsets({60.0f, 90.0f}, 60.0f, vsyncPhaseOffsetNs, sfVSyncPhaseOffsetNs,
-                               earlySfOffsetNs, earlyGpuSfOffsetNs, earlyAppOffsetNs,
-                               earlyGpuAppOffsetNs, highFpsVsyncPhaseOffsetNs,
+          : impl::PhaseOffsets({Fps(60.0f), Fps(90.0f)}, Fps(60.0f), vsyncPhaseOffsetNs,
+                               sfVSyncPhaseOffsetNs, earlySfOffsetNs, earlyGpuSfOffsetNs,
+                               earlyAppOffsetNs, earlyGpuAppOffsetNs, highFpsVsyncPhaseOffsetNs,
                                highFpsSfVSyncPhaseOffsetNs, highFpsEarlySfOffsetNs,
                                highFpsEarlyGpuSfOffsetNs, highFpsEarlyAppOffsetNs,
                                highFpsEarlyGpuAppOffsetNs, thresholdForNextVsync) {}
@@ -190,7 +190,7 @@
 };
 
 TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_unknownRefreshRate) {
-    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(14.7f);
+    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(Fps(14.7f));
 
     EXPECT_EQ(offsets.late.sfOffset, 6'000'000);
     EXPECT_EQ(offsets.late.appOffset, 2'000'000);
@@ -212,7 +212,7 @@
 }
 
 TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_60Hz) {
-    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(60.0f);
+    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(Fps(60.0f));
 
     EXPECT_EQ(offsets.late.sfOffset, 6'000'000);
     EXPECT_EQ(offsets.late.appOffset, 2'000'000);
@@ -234,7 +234,7 @@
 }
 
 TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_90Hz) {
-    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(90.0f);
+    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(Fps(90.0f));
 
     EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
     EXPECT_EQ(offsets.late.appOffset, 2'000'000);
@@ -258,7 +258,7 @@
 TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_DefaultValues_60Hz) {
     TestablePhaseOffsets phaseOffsets{1'000'000, 1'000'000, {}, {}, {}, {},        2'000'000,
                                       1'000'000, {},        {}, {}, {}, 10'000'000};
-    auto offsets = phaseOffsets.getConfigsForRefreshRate(60.0f);
+    auto offsets = phaseOffsets.getConfigsForRefreshRate(Fps(60.0f));
 
     EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
     EXPECT_EQ(offsets.late.appOffset, 1'000'000);
@@ -282,7 +282,7 @@
 TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_DefaultValues_90Hz) {
     TestablePhaseOffsets phaseOffsets{1'000'000, 1'000'000, {}, {}, {}, {},        2'000'000,
                                       1'000'000, {},        {}, {}, {}, 10'000'000};
-    auto offsets = phaseOffsets.getConfigsForRefreshRate(90.0f);
+    auto offsets = phaseOffsets.getConfigsForRefreshRate(Fps(90.0f));
 
     EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
     EXPECT_EQ(offsets.late.appOffset, 2'000'000);