Add shouldBeSeamless to setFrameRate

This CL adds a new parameter shouldBeSeamless to the existing
setFrameRate APIs. This parameter indicates whether the desired
refresh rate should be achieved only seamlessly or also switches
with visual interruptions for the user are allowed. The default
value of the new parameter is "true".

Test: atest RefreshRateConfigsTest
Test: atest SetFrameRateTest
Test: atest libsurfaceflinger_unittest
Test: atest libgui_test

Bug: 161776961
Change-Id: I0df16e09f77c8c198fd3733fb581a2aaadfed685
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 9481966..79e2ad0 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -1687,8 +1687,9 @@
                   crop.bottom);
     if (layerState.frameRate.rate != 0 ||
         layerState.frameRate.type != FrameRateCompatibility::Default) {
-        StringAppendF(&result, "% 6.2ffps %15s", layerState.frameRate.rate,
-                      frameRateCompatibilityString(layerState.frameRate.type).c_str());
+        StringAppendF(&result, "% 6.2ffps %15s seamless=%d", layerState.frameRate.rate,
+                      frameRateCompatibilityString(layerState.frameRate.type).c_str(),
+                      layerState.frameRate.shouldBeSeamless);
     } else {
         StringAppendF(&result, "                         ");
     }
@@ -2750,6 +2751,12 @@
 
 // ---------------------------------------------------------------------------
 
+std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate) {
+    return stream << "{rate=" << rate.rate
+                  << " type=" << Layer::frameRateCompatibilityString(rate.type)
+                  << " shouldBeSeamless=" << rate.shouldBeSeamless << "}";
+}
+
 }; // namespace android
 
 #if defined(__gl_h_)
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index b1ab9ec..1a784aa 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -153,12 +153,15 @@
     struct FrameRate {
         float rate;
         FrameRateCompatibility type;
+        bool shouldBeSeamless;
 
-        FrameRate() : rate(0), type(FrameRateCompatibility::Default) {}
-        FrameRate(float rate, FrameRateCompatibility type) : rate(rate), type(type) {}
+        FrameRate() : rate(0), type(FrameRateCompatibility::Default), shouldBeSeamless(true) {}
+        FrameRate(float rate, FrameRateCompatibility type, bool shouldBeSeamless = true)
+              : rate(rate), type(type), shouldBeSeamless(shouldBeSeamless) {}
 
         bool operator==(const FrameRate& other) const {
-            return rate == other.rate && type == other.type;
+            return rate == other.rate && type == other.type &&
+                    shouldBeSeamless == other.shouldBeSeamless;
         }
 
         bool operator!=(const FrameRate& other) const { return !(*this == other); }
@@ -1126,4 +1129,6 @@
     const std::vector<BlurRegion>& getBlurRegions() const;
 };
 
+std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate);
+
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 36433c2..28af930 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -125,12 +125,23 @@
                             return LayerVoteType::NoVote;
                     }
                 }();
-                summary.push_back({layer->getName(), voteType, frameRate.rate, /* weight */ 1.0f,
-                                   layerFocused});
+                summary.push_back(
+                        RefreshRateConfigs::LayerRequirement{.name = layer->getName(),
+                                                             .vote = voteType,
+                                                             .desiredRefreshRate = frameRate.rate,
+                                                             .shouldBeSeamless =
+                                                                     frameRate.shouldBeSeamless,
+                                                             .weight = 1.0f,
+                                                             .focused = layerFocused});
             } else if (recent) {
-                summary.push_back({layer->getName(), LayerVoteType::Heuristic,
-                                   info->getRefreshRate(now),
-                                   /* weight */ 1.0f, layerFocused});
+                summary.push_back(
+                        RefreshRateConfigs::LayerRequirement{.name = layer->getName(),
+                                                             .vote = LayerVoteType::Heuristic,
+                                                             .desiredRefreshRate =
+                                                                     info->getRefreshRate(now),
+                                                             .shouldBeSeamless = true,
+                                                             .weight = 1.0f,
+                                                             .focused = layerFocused});
             }
 
             if (CC_UNLIKELY(mTraceEnabled)) {
diff --git a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
index 37e67e1..a63ccc1 100644
--- a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
@@ -130,9 +130,9 @@
         ALOGV("%s has priority: %d %s focused", strong->getName().c_str(),
               frameRateSelectionPriority, layerFocused ? "" : "not");
 
-        const auto [type, refreshRate] = info->getRefreshRate(now);
+        const auto vote = info->getRefreshRateVote(now);
         // Skip NoVote layer as those don't have any requirements
-        if (type == LayerHistory::LayerVoteType::NoVote) {
+        if (vote.type == LayerHistory::LayerVoteType::NoVote) {
             continue;
         }
 
@@ -144,10 +144,11 @@
 
         const float layerArea = transformed.getWidth() * transformed.getHeight();
         float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0f;
-        summary.push_back({strong->getName(), type, refreshRate, weight, layerFocused});
+        summary.push_back({strong->getName(), vote.type, vote.fps, vote.shouldBeSeamless, weight,
+                           layerFocused});
 
         if (CC_UNLIKELY(mTraceEnabled)) {
-            trace(layer, *info, type, static_cast<int>(std::round(refreshRate)));
+            trace(layer, *info, vote.type, static_cast<int>(std::round(vote.fps)));
         }
     }
 
@@ -178,7 +179,7 @@
 
             if (frameRate.rate > 0 || voteType == LayerVoteType::NoVote) {
                 const auto type = layer->isVisible() ? voteType : LayerVoteType::NoVote;
-                info->setLayerVote(type, frameRate.rate);
+                info->setLayerVote({type, frameRate.rate, frameRate.shouldBeSeamless});
             } else {
                 info->resetLayerVote();
             }
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
index 44f20d0..94e7e20 100644
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
@@ -198,10 +198,10 @@
                                           : std::make_optional(mLastRefreshRate.reported);
 }
 
-std::pair<LayerHistory::LayerVoteType, float> LayerInfoV2::getRefreshRate(nsecs_t now) {
+LayerInfoV2::LayerVote LayerInfoV2::getRefreshRateVote(nsecs_t now) {
     if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
         ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));
-        return {mLayerVote.type, mLayerVote.fps};
+        return mLayerVote;
     }
 
     if (isAnimating(now)) {
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.h b/services/surfaceflinger/Scheduler/LayerInfoV2.h
index 33dc66f..2305bc3 100644
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.h
+++ b/services/surfaceflinger/Scheduler/LayerInfoV2.h
@@ -56,6 +56,13 @@
     friend class LayerHistoryTestV2;
 
 public:
+    // Holds information about the layer vote
+    struct LayerVote {
+        LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
+        float fps = 0.0f;
+        bool shouldBeSeamless = true;
+    };
+
     static void setTraceEnabled(bool enabled) { sTraceEnabled = enabled; }
 
     static void setRefreshRateConfigs(const RefreshRateConfigs& refreshRateConfigs) {
@@ -76,7 +83,7 @@
 
     // Sets an explicit layer vote. This usually comes directly from the application via
     // ANativeWindow_setFrameRate API
-    void setLayerVote(LayerHistory::LayerVoteType type, float fps) { mLayerVote = {type, fps}; }
+    void setLayerVote(LayerVote vote) { mLayerVote = vote; }
 
     // Sets the default layer vote. This will be the layer vote after calling to resetLayerVote().
     // This is used for layers that called to setLayerVote() and then removed the vote, so that the
@@ -84,9 +91,9 @@
     void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; }
 
     // Resets the layer vote to its default.
-    void resetLayerVote() { mLayerVote = {mDefaultVote, 0.0f}; }
+    void resetLayerVote() { mLayerVote = {mDefaultVote, 0.0f, true}; }
 
-    std::pair<LayerHistory::LayerVoteType, float> getRefreshRate(nsecs_t now);
+    LayerVote getRefreshRateVote(nsecs_t now);
 
     // Return the last updated time. If the present time is farther in the future than the
     // updated time, the updated time is the present time.
@@ -130,12 +137,6 @@
         bool animatingOrInfrequent = false;
     };
 
-    // Holds information about the layer vote
-    struct LayerVote {
-        LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
-        float fps = 0.0f;
-    };
-
     // Class to store past calculated refresh rate and determine whether
     // the refresh rate calculated is consistent with past values
     class RefreshRateHistory {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 150f925..0646c7d 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -31,6 +31,12 @@
 using AllRefreshRatesMapType = RefreshRateConfigs::AllRefreshRatesMapType;
 using RefreshRate = RefreshRateConfigs::RefreshRate;
 
+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(),
+                              hwcConfig->getWidth(), hwcConfig->getHeight(), getConfigGroup());
+}
+
 std::string RefreshRateConfigs::layerVoteTypeString(LayerVoteType vote) {
     switch (vote) {
         case LayerVoteType::NoVote:
@@ -125,7 +131,7 @@
         const std::vector<LayerRequirement>& layers, const GlobalSignals& globalSignals,
         GlobalSignals* outSignalsConsidered) const {
     ATRACE_CALL();
-    ALOGV("getRefreshRateForContent %zu layers", layers.size());
+    ALOGV("getBestRefreshRate %zu layers", layers.size());
 
     if (outSignalsConsidered) *outSignalsConsidered = {};
     const auto setTouchConsidered = [&] {
@@ -148,6 +154,7 @@
     int explicitDefaultVoteLayers = 0;
     int explicitExactOrMultipleVoteLayers = 0;
     float maxExplicitWeight = 0;
+    int seamedLayers = 0;
     for (const auto& layer : layers) {
         if (layer.vote == LayerVoteType::NoVote) {
             noVoteLayers++;
@@ -162,6 +169,10 @@
             explicitExactOrMultipleVoteLayers++;
             maxExplicitWeight = std::max(maxExplicitWeight, layer.weight);
         }
+
+        if (!layer.shouldBeSeamless) {
+            seamedLayers++;
+        }
     }
 
     const bool hasExplicitVoteLayers =
@@ -206,6 +217,8 @@
         scores.emplace_back(refreshRate, 0.0f);
     }
 
+    const auto& defaultConfig = mRefreshRates.at(policy->defaultConfig);
+
     for (const auto& layer : layers) {
         ALOGV("Calculating score for %s (%s, weight %.2f)", layer.name.c_str(),
               layerVoteTypeString(layer.vote).c_str(), layer.weight);
@@ -216,6 +229,30 @@
         auto weight = layer.weight;
 
         for (auto i = 0u; i < scores.size(); i++) {
+            // If there are no layers with shouldBeSeamless=false and the current
+            // config group is different from the default one, this means a layer with
+            // shouldBeSeamless=false has just disappeared and we should switch back to
+            // the default config group.
+            const bool isSeamlessSwitch = seamedLayers > 0
+                    ? scores[i].first->getConfigGroup() == mCurrentRefreshRate->getConfigGroup()
+                    : scores[i].first->getConfigGroup() == defaultConfig->getConfigGroup();
+
+            if (layer.shouldBeSeamless && !isSeamlessSwitch) {
+                ALOGV("%s (weight %.2f) ignores %s (group=%d) to avoid non-seamless switch."
+                      "Current config = %s",
+                      layer.name.c_str(), weight, scores[i].first->name.c_str(),
+                      scores[i].first->getConfigGroup(), mCurrentRefreshRate->toString().c_str());
+                continue;
+            }
+
+            if (!layer.shouldBeSeamless && !isSeamlessSwitch && !layer.focused) {
+                ALOGV("%s (weight %.2f) ignores %s (group=%d) because it's not focused"
+                      " and the switch is going to be seamed. Current config = %s",
+                      layer.name.c_str(), weight, scores[i].first->name.c_str(),
+                      scores[i].first->getConfigGroup(), mCurrentRefreshRate->toString().c_str());
+                continue;
+            }
+
             bool inPrimaryRange =
                     scores[i].first->inPolicy(policy->primaryRange.min, policy->primaryRange.max);
             if ((primaryRangeIsSingleRate || !inPrimaryRange) &&
@@ -292,10 +329,13 @@
 
                     return 1.0f / iter;
                 }();
+                // Slightly prefer seamless switches.
+                constexpr float kSeamedSwitchPenalty = 0.95f;
+                const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty;
                 ALOGV("%s (%s, weight %.2f) %.2fHz gives %s score of %.2f", layer.name.c_str(),
                       layerVoteTypeString(layer.vote).c_str(), weight, 1e9f / layerPeriod,
                       scores[i].first->name.c_str(), layerScore);
-                scores[i].second += weight * layerScore;
+                scores[i].second += weight * layerScore * seamlessness;
                 continue;
             }
         }
@@ -367,6 +407,15 @@
 }
 
 const RefreshRate& RefreshRateConfigs::getMinRefreshRateByPolicyLocked() const {
+    for (auto refreshRate : mPrimaryRefreshRates) {
+        if (mCurrentRefreshRate->getConfigGroup() == refreshRate->getConfigGroup()) {
+            return *refreshRate;
+        }
+    }
+    ALOGE("Can't find min refresh rate by policy with the same config group"
+          " as the current config %s",
+          mCurrentRefreshRate->toString().c_str());
+    // Defaulting to the lowest refresh rate
     return *mPrimaryRefreshRates.front();
 }
 
@@ -376,6 +425,16 @@
 }
 
 const RefreshRate& RefreshRateConfigs::getMaxRefreshRateByPolicyLocked() const {
+    for (auto it = mPrimaryRefreshRates.rbegin(); it != mPrimaryRefreshRates.rend(); it++) {
+        const auto& refreshRate = (**it);
+        if (mCurrentRefreshRate->getConfigGroup() == refreshRate.getConfigGroup()) {
+            return refreshRate;
+        }
+    }
+    ALOGE("Can't find max refresh rate by policy with the same config group"
+          " as the current config %s",
+          mCurrentRefreshRate->toString().c_str());
+    // Defaulting to the highest refresh rate
     return *mPrimaryRefreshRates.back();
 }
 
@@ -414,7 +473,7 @@
         const float fps = 1e9f / config->getVsyncPeriod();
         mRefreshRates.emplace(configId,
                               std::make_unique<RefreshRate>(configId, config,
-                                                            base::StringPrintf("%.0ffps", fps), fps,
+                                                            base::StringPrintf("%.2ffps", fps), fps,
                                                             RefreshRate::ConstructorTag(0)));
         if (configId == currentConfigId) {
             mCurrentRefreshRate = mRefreshRates.at(configId).get();
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 8ff92a0..41e54a7 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -86,6 +86,8 @@
 
         bool operator==(const RefreshRate& other) const { return !(*this != other); }
 
+        std::string toString() const;
+
     private:
         friend RefreshRateConfigs;
         friend class RefreshRateConfigsTest;
@@ -216,6 +218,8 @@
         LayerVoteType vote = LayerVoteType::NoVote;
         // Layer's desired refresh rate, if applicable.
         float desiredRefreshRate = 0.0f;
+        // If a seamless mode switch is required.
+        bool shouldBeSeamless = true;
         // Layer's weight in the range of [0, 1]. The higher the weight the more impact this layer
         // would have on choosing the refresh rate.
         float weight = 0.0f;
diff --git a/services/surfaceflinger/Scheduler/StrongTyping.h b/services/surfaceflinger/Scheduler/StrongTyping.h
index e8ca0ba..6a60257 100644
--- a/services/surfaceflinger/Scheduler/StrongTyping.h
+++ b/services/surfaceflinger/Scheduler/StrongTyping.h
@@ -70,6 +70,10 @@
     T const& value() const { return mValue; }
     T& value() { return mValue; }
 
+    friend std::ostream& operator<<(std::ostream& os, const StrongTyping<T, W, Ability...>& value) {
+        return os << value.value();
+    }
+
 private:
     T mValue;
 };
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 57c4d52..28a8e7a 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -3781,7 +3781,8 @@
                               "SurfaceFlinger::setClientStateLocked") &&
             layer->setFrameRate(Layer::FrameRate(s.frameRate,
                                                  Layer::FrameRate::convertCompatibility(
-                                                         s.frameRateCompatibility)))) {
+                                                         s.frameRateCompatibility),
+                                                 s.shouldBeSeamless))) {
             flags |= eTraversalNeeded;
         }
     }
@@ -6135,7 +6136,7 @@
 }
 
 status_t SurfaceFlinger::setFrameRate(const sp<IGraphicBufferProducer>& surface, float frameRate,
-                                      int8_t compatibility) {
+                                      int8_t compatibility, bool shouldBeSeamless) {
     if (!ValidateFrameRate(frameRate, compatibility, "SurfaceFlinger::setFrameRate")) {
         return BAD_VALUE;
     }
@@ -6148,10 +6149,10 @@
                 ALOGE("Attempt to set frame rate on a layer that no longer exists");
                 return BAD_VALUE;
             }
-
             if (layer->setFrameRate(
                         Layer::FrameRate(frameRate,
-                                         Layer::FrameRate::convertCompatibility(compatibility)))) {
+                                         Layer::FrameRate::convertCompatibility(compatibility),
+                                         shouldBeSeamless))) {
                 setTransactionFlags(eTraversalNeeded);
             }
         } else {
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index a821d44..9666f14 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -600,7 +600,7 @@
     status_t setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor,
                                      float lightPosY, float lightPosZ, float lightRadius) override;
     status_t setFrameRate(const sp<IGraphicBufferProducer>& surface, float frameRate,
-                          int8_t compatibility) override;
+                          int8_t compatibility, bool shouldBeSeamless) override;
     status_t acquireFrameRateFlexibilityToken(sp<IBinder>* outToken) override;
 
     status_t setFrameTimelineVsync(const sp<IGraphicBufferProducer>& surface,
diff --git a/services/surfaceflinger/tests/LayerTransactionTest.h b/services/surfaceflinger/tests/LayerTransactionTest.h
index da71dad..b87c734 100644
--- a/services/surfaceflinger/tests/LayerTransactionTest.h
+++ b/services/surfaceflinger/tests/LayerTransactionTest.h
@@ -123,7 +123,7 @@
     }
 
     virtual void fillBufferQueueLayerColor(const sp<SurfaceControl>& layer, const Color& color,
-                                           int32_t bufferWidth, int32_t bufferHeight) {
+                                           uint32_t bufferWidth, uint32_t bufferHeight) {
         ANativeWindow_Buffer buffer;
         ASSERT_NO_FATAL_FAILURE(buffer = getBufferQueueLayerBuffer(layer));
         TransactionUtils::fillANativeWindowBufferColor(buffer,
@@ -145,7 +145,7 @@
     }
 
     void fillLayerColor(uint32_t mLayerType, const sp<SurfaceControl>& layer, const Color& color,
-                        int32_t bufferWidth, int32_t bufferHeight) {
+                        uint32_t bufferWidth, uint32_t bufferHeight) {
         switch (mLayerType) {
             case ISurfaceComposerClient::eFXSurfaceBufferQueue:
                 fillBufferQueueLayerColor(layer, color, bufferWidth, bufferHeight);
diff --git a/services/surfaceflinger/tests/SetFrameRate_test.cpp b/services/surfaceflinger/tests/SetFrameRate_test.cpp
index 02ba9e2..d1bed0c 100644
--- a/services/surfaceflinger/tests/SetFrameRate_test.cpp
+++ b/services/surfaceflinger/tests/SetFrameRate_test.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <system/window.h>
 
 #include <thread>
@@ -50,8 +46,8 @@
         }
     }
 
-    const int mLayerWidth = 32;
-    const int mLayerHeight = 32;
+    const uint32_t mLayerWidth = 32;
+    const uint32_t mLayerHeight = 32;
     sp<SurfaceControl> mLayer;
     uint32_t mLayerType;
 };
@@ -59,26 +55,27 @@
 TEST_F(SetFrameRateTest, BufferQueueLayerSetFrameRate) {
     CreateLayer(ISurfaceComposerClient::eFXSurfaceBufferQueue);
     native_window_set_frame_rate(mLayer->getSurface().get(), 100.f,
-                                 ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT);
+                                 ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                 /* shouldBeSeamless */ true);
     ASSERT_NO_FATAL_FAILURE(PostBuffers(Color::RED));
     Transaction()
-            .setFrameRate(mLayer, 200.f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT)
+            .setFrameRate(mLayer, 200.f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                          /* shouldBeSeamless */ true)
             .apply();
     ASSERT_NO_FATAL_FAILURE(PostBuffers(Color::RED));
     native_window_set_frame_rate(mLayer->getSurface().get(), 300.f,
-                                 ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT);
+                                 ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                 /* shouldBeSeamless */ true);
     ASSERT_NO_FATAL_FAILURE(PostBuffers(Color::RED));
 }
 
 TEST_F(SetFrameRateTest, BufferStateLayerSetFrameRate) {
     CreateLayer(ISurfaceComposerClient::eFXSurfaceBufferState);
     Transaction()
-            .setFrameRate(mLayer, 400.f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT)
+            .setFrameRate(mLayer, 400.f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                          /* shouldBeSeamless */ true)
             .apply();
     ASSERT_NO_FATAL_FAILURE(PostBuffers(Color::GREEN));
 }
 
 } // namespace android
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/TransactionTestHarnesses.h b/services/surfaceflinger/tests/TransactionTestHarnesses.h
index 01badf4..a361b1e 100644
--- a/services/surfaceflinger/tests/TransactionTestHarnesses.h
+++ b/services/surfaceflinger/tests/TransactionTestHarnesses.h
@@ -98,14 +98,14 @@
                                                  outTransformHint, format);
     }
 
-    void fillLayerColor(const sp<SurfaceControl>& layer, const Color& color, int32_t bufferWidth,
-                        int32_t bufferHeight) {
+    void fillLayerColor(const sp<SurfaceControl>& layer, const Color& color, uint32_t bufferWidth,
+                        uint32_t bufferHeight) {
         ASSERT_NO_FATAL_FAILURE(LayerTransactionTest::fillLayerColor(mLayerType, layer, color,
                                                                      bufferWidth, bufferHeight));
     }
 
-    void fillLayerQuadrant(const sp<SurfaceControl>& layer, int32_t bufferWidth,
-                           int32_t bufferHeight, const Color& topLeft, const Color& topRight,
+    void fillLayerQuadrant(const sp<SurfaceControl>& layer, uint32_t bufferWidth,
+                           uint32_t bufferHeight, const Color& topLeft, const Color& topRight,
                            const Color& bottomLeft, const Color& bottomRight) {
         ASSERT_NO_FATAL_FAILURE(LayerTransactionTest::fillLayerQuadrant(mLayerType, layer,
                                                                         bufferWidth, bufferHeight,
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
index cb376cd..3b50321 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
@@ -78,7 +78,7 @@
         for (auto& [weak, info] : history().mLayerInfos) {
             if (auto strong = weak.promote(); strong && strong.get() == layer) {
                 info->setDefaultLayerVote(vote);
-                info->setLayerVote(vote, 0);
+                info->setLayerVote({vote, 0, false});
                 return;
             }
         }
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 4762fd4..df76110 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -57,6 +57,8 @@
     static inline const HwcConfigIndexType HWC_CONFIG_ID_72 = HwcConfigIndexType(2);
     static inline const HwcConfigIndexType HWC_CONFIG_ID_120 = HwcConfigIndexType(3);
     static inline const HwcConfigIndexType HWC_CONFIG_ID_30 = HwcConfigIndexType(4);
+    static inline const HwcConfigIndexType HWC_CONFIG_ID_25 = HwcConfigIndexType(5);
+    static inline const HwcConfigIndexType HWC_CONFIG_ID_50 = HwcConfigIndexType(6);
 
     // Test configs
     std::shared_ptr<const HWC2::Display::Config> mConfig60 =
@@ -77,8 +79,16 @@
             createConfig(HWC_CONFIG_ID_120, 1, static_cast<int64_t>(1e9f / 120));
     std::shared_ptr<const HWC2::Display::Config> mConfig30 =
             createConfig(HWC_CONFIG_ID_30, 0, static_cast<int64_t>(1e9f / 30));
+    std::shared_ptr<const HWC2::Display::Config> mConfig30DifferentGroup =
+            createConfig(HWC_CONFIG_ID_30, 1, static_cast<int64_t>(1e9f / 30));
+    std::shared_ptr<const HWC2::Display::Config> mConfig25DifferentGroup =
+            createConfig(HWC_CONFIG_ID_25, 1, static_cast<int64_t>(1e9f / 25));
+    std::shared_ptr<const HWC2::Display::Config> mConfig50 =
+            createConfig(HWC_CONFIG_ID_50, 0, static_cast<int64_t>(1e9f / 50));
 
     // Test device configurations
+    // The positions of the configs in the arrays below MUST match their IDs. For example,
+    // the first config should always be 60Hz, the second 90Hz etc.
     std::vector<std::shared_ptr<const HWC2::Display::Config>> m60OnlyConfigDevice = {mConfig60};
     std::vector<std::shared_ptr<const HWC2::Display::Config>> m60_90Device = {mConfig60, mConfig90};
     std::vector<std::shared_ptr<const HWC2::Display::Config>> m60_90DeviceWithDifferentGroups =
@@ -104,6 +114,14 @@
             {mConfig60, mConfig90, mConfig72, mConfig120DifferentGroup, mConfig30};
     std::vector<std::shared_ptr<const HWC2::Display::Config>> m30_60_90Device =
             {mConfig60, mConfig90, mConfig72DifferentGroup, mConfig120DifferentGroup, mConfig30};
+    std::vector<std::shared_ptr<const HWC2::Display::Config>> m25_30_50_60Device =
+            {mConfig60,
+             mConfig90,
+             mConfig72DifferentGroup,
+             mConfig120DifferentGroup,
+             mConfig30DifferentGroup,
+             mConfig25DifferentGroup,
+             mConfig50};
 
     // Expected RefreshRate objects
     RefreshRate mExpected60Config = {HWC_CONFIG_ID_60, mConfig60, "60fps", 60,
@@ -292,8 +310,8 @@
                                                  /*currentConfigId=*/HWC_CONFIG_ID_60);
 
     const auto makeLayerRequirements = [](float refreshRate) -> std::vector<LayerRequirement> {
-        return {{"testLayer", LayerVoteType::Heuristic, refreshRate, /*weight*/ 1.0f,
-                 /*focused*/ false}};
+        return {{"testLayer", LayerVoteType::Heuristic, refreshRate, /*shouldBeSeamless*/ true,
+                 /*weight*/ 1.0f, /*focused*/ false}};
     };
 
     EXPECT_EQ(mExpected90Config,
@@ -1245,7 +1263,9 @@
     auto& layer = layers[0];
     layer.vote = LayerVoteType::ExplicitDefault;
     layer.desiredRefreshRate = 90.0f;
+    layer.shouldBeSeamless = false;
     layer.name = "90Hz ExplicitDefault";
+    layer.focused = true;
 
     ASSERT_EQ(HWC_CONFIG_ID_60,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
@@ -1258,6 +1278,104 @@
     ASSERT_EQ(HWC_CONFIG_ID_90,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
                       .getConfigId());
+
+    // Verify that we won't change the group if seamless switch is required.
+    layer.shouldBeSeamless = true;
+    ASSERT_EQ(HWC_CONFIG_ID_60,
+              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
+                      .getConfigId());
+
+    // At this point the default config in the DisplayManager policy with be 60Hz.
+    // Verify that if the current config is in another group and there are no layers with
+    // shouldBeSeamless=false we'll go back to the default group.
+    refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
+    layer.desiredRefreshRate = 60.0f;
+    layer.name = "60Hz ExplicitDefault";
+    layer.shouldBeSeamless = true;
+    ASSERT_EQ(HWC_CONFIG_ID_60,
+              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
+                      .getConfigId());
+
+    // If there's a layer with shouldBeSeamless=false, another layer with shouldBeSeamless=true
+    // can't change the config group.
+    refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
+    auto layer2 = LayerRequirement{.weight = 0.5f};
+    layer2.vote = LayerVoteType::ExplicitDefault;
+    layer2.desiredRefreshRate = 90.0f;
+    layer2.name = "90Hz ExplicitDefault";
+    layer2.shouldBeSeamless = false;
+    layer2.focused = false;
+    layers.push_back(layer2);
+    ASSERT_EQ(HWC_CONFIG_ID_90,
+              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
+                      .getConfigId());
+}
+
+TEST_F(RefreshRateConfigsTest, nonSeamlessVotePrefersSeamlessSwitches) {
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(m30_60Device,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+    // Allow group switching.
+    RefreshRateConfigs::Policy policy;
+    policy.defaultConfig = refreshRateConfigs->getCurrentPolicy().defaultConfig;
+    policy.allowGroupSwitching = true;
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    auto& layer = layers[0];
+    layer.vote = LayerVoteType::ExplicitExactOrMultiple;
+    layer.desiredRefreshRate = 60.0f;
+    layer.shouldBeSeamless = false;
+    layer.name = "60Hz ExplicitExactOrMultiple";
+    layer.focused = true;
+
+    ASSERT_EQ(HWC_CONFIG_ID_60,
+              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
+                      .getConfigId());
+
+    refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_120);
+    ASSERT_EQ(HWC_CONFIG_ID_120,
+              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
+                      .getConfigId());
+}
+
+TEST_F(RefreshRateConfigsTest, nonSeamlessExactAndSeamlessMultipleLayers) {
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(m25_30_50_60Device,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+    // Allow group switching.
+    RefreshRateConfigs::Policy policy;
+    policy.defaultConfig = refreshRateConfigs->getCurrentPolicy().defaultConfig;
+    policy.allowGroupSwitching = true;
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+
+    auto layers = std::vector<
+            LayerRequirement>{LayerRequirement{.name = "60Hz ExplicitDefault",
+                                               .vote = LayerVoteType::ExplicitDefault,
+                                               .desiredRefreshRate = 60.0f,
+                                               .shouldBeSeamless = false,
+                                               .weight = 0.5f,
+                                               .focused = false},
+                              LayerRequirement{.name = "25Hz ExplicitExactOrMultiple",
+                                               .vote = LayerVoteType::ExplicitExactOrMultiple,
+                                               .desiredRefreshRate = 25.0f,
+                                               .shouldBeSeamless = true,
+                                               .weight = 1.0f,
+                                               .focused = true}};
+    auto& seamedLayer = layers[0];
+
+    ASSERT_EQ(HWC_CONFIG_ID_50,
+              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
+                      .getConfigId());
+
+    seamedLayer.name = "30Hz ExplicitDefault", seamedLayer.desiredRefreshRate = 30.0f;
+    refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_30);
+
+    ASSERT_EQ(HWC_CONFIG_ID_25,
+              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
+                      .getConfigId());
 }
 
 TEST_F(RefreshRateConfigsTest, primaryVsAppRequestPolicy) {
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
index de66f8f..d0bb9e2 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
@@ -108,7 +108,7 @@
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
     EXPECT_LT(screenOff, times["ScreenOff"]);
-    EXPECT_EQ(0u, times.count("90fps"));
+    EXPECT_EQ(0u, times.count("90.00fps"));
 
     mRefreshRateStats->setConfigMode(CONFIG_ID_0);
     mRefreshRateStats->setPowerMode(PowerMode::ON);
@@ -116,15 +116,15 @@
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
     EXPECT_EQ(screenOff, times["ScreenOff"]);
-    ASSERT_EQ(1u, times.count("90fps"));
-    EXPECT_LT(0, times["90fps"]);
+    ASSERT_EQ(1u, times.count("90.00fps"));
+    EXPECT_LT(0, times["90.00fps"]);
 
     mRefreshRateStats->setPowerMode(PowerMode::DOZE);
-    int ninety = mRefreshRateStats->getTotalTimes()["90fps"];
+    int ninety = mRefreshRateStats->getTotalTimes()["90.00fps"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
     EXPECT_LT(screenOff, times["ScreenOff"]);
-    EXPECT_EQ(ninety, times["90fps"]);
+    EXPECT_EQ(ninety, times["90.00fps"]);
 
     mRefreshRateStats->setConfigMode(CONFIG_ID_0);
     screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
@@ -133,7 +133,7 @@
     // Because the power mode is not PowerMode::ON, switching the config
     // does not update refresh rates that come from the config.
     EXPECT_LT(screenOff, times["ScreenOff"]);
-    EXPECT_EQ(ninety, times["90fps"]);
+    EXPECT_EQ(ninety, times["90.00fps"]);
 }
 
 TEST_F(RefreshRateStatsTest, twoConfigsTest) {
@@ -163,53 +163,53 @@
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
     EXPECT_EQ(screenOff, times["ScreenOff"]);
-    ASSERT_EQ(1u, times.count("90fps"));
-    EXPECT_LT(0, times["90fps"]);
+    ASSERT_EQ(1u, times.count("90.00fps"));
+    EXPECT_LT(0, times["90.00fps"]);
 
     // When power mode is normal, time for configs updates.
     mRefreshRateStats->setConfigMode(CONFIG_ID_1);
-    int ninety = mRefreshRateStats->getTotalTimes()["90fps"];
+    int ninety = mRefreshRateStats->getTotalTimes()["90.00fps"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
     EXPECT_EQ(screenOff, times["ScreenOff"]);
-    EXPECT_EQ(ninety, times["90fps"]);
-    ASSERT_EQ(1u, times.count("60fps"));
-    EXPECT_LT(0, times["60fps"]);
+    EXPECT_EQ(ninety, times["90.00fps"]);
+    ASSERT_EQ(1u, times.count("60.00fps"));
+    EXPECT_LT(0, times["60.00fps"]);
 
     mRefreshRateStats->setConfigMode(CONFIG_ID_0);
-    int sixty = mRefreshRateStats->getTotalTimes()["60fps"];
+    int sixty = mRefreshRateStats->getTotalTimes()["60.00fps"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
     EXPECT_EQ(screenOff, times["ScreenOff"]);
-    EXPECT_LT(ninety, times["90fps"]);
-    EXPECT_EQ(sixty, times["60fps"]);
+    EXPECT_LT(ninety, times["90.00fps"]);
+    EXPECT_EQ(sixty, times["60.00fps"]);
 
     mRefreshRateStats->setConfigMode(CONFIG_ID_1);
-    ninety = mRefreshRateStats->getTotalTimes()["90fps"];
+    ninety = mRefreshRateStats->getTotalTimes()["90.00fps"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
     EXPECT_EQ(screenOff, times["ScreenOff"]);
-    EXPECT_EQ(ninety, times["90fps"]);
-    EXPECT_LT(sixty, times["60fps"]);
+    EXPECT_EQ(ninety, times["90.00fps"]);
+    EXPECT_LT(sixty, times["60.00fps"]);
 
     // Because the power mode is not PowerMode::ON, switching the config
     // does not update refresh rates that come from the config.
     mRefreshRateStats->setPowerMode(PowerMode::DOZE);
     mRefreshRateStats->setConfigMode(CONFIG_ID_0);
-    sixty = mRefreshRateStats->getTotalTimes()["60fps"];
+    sixty = mRefreshRateStats->getTotalTimes()["60.00fps"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
     EXPECT_LT(screenOff, times["ScreenOff"]);
-    EXPECT_EQ(ninety, times["90fps"]);
-    EXPECT_EQ(sixty, times["60fps"]);
+    EXPECT_EQ(ninety, times["90.00fps"]);
+    EXPECT_EQ(sixty, times["60.00fps"]);
 
     mRefreshRateStats->setConfigMode(CONFIG_ID_1);
     screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
     EXPECT_LT(screenOff, times["ScreenOff"]);
-    EXPECT_EQ(ninety, times["90fps"]);
-    EXPECT_EQ(sixty, times["60fps"]);
+    EXPECT_EQ(ninety, times["90.00fps"]);
+    EXPECT_EQ(sixty, times["60.00fps"]);
 }
 } // namespace
 } // namespace scheduler