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/include/android/surface_control.h b/include/android/surface_control.h
index cbcf6ec..9758c9c 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -410,24 +410,10 @@
 #if __ANDROID_API__ >= 30
 
 /**
- * Sets the intended frame rate for |surface_control|.
+ * Same as ASurfaceTransaction_setFrameRateWithSeamlessness(transaction, surface_control,
+ * frameRate, compatibility, true).
  *
- * On devices that are capable of running the display at different refresh rates, the system may
- * choose a display refresh rate to better match this surface's frame rate. Usage of this API won't
- * directly affect the application's frame production pipeline. However, because the system may
- * change the display refresh rate, calls to this function may result in changes to Choreographer
- * callback timings, and changes to the time interval at which the system releases buffers back to
- * the application.
- *
- * |frameRate| is the intended frame rate of this surface, in frames per second. 0 is a special
- * value that indicates the app will accept the system's choice for the display frame rate, which is
- * the default behavior if this function isn't called. The frameRate param does <em>not</em> need to
- * be a valid refresh rate for this device's display - e.g., it's fine to pass 30fps to a device
- * that can only run the display at 60fps.
- *
- * |compatibility| The frame rate compatibility of this surface. The compatibility value may
- * influence the system's choice of display frame rate. To specify a compatibility use the
- * ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_* enum.
+ * See ASurfaceTransaction_setFrameRateWithSeamlessness().
  *
  * Available since API level 30.
  */
@@ -437,6 +423,42 @@
 
 #endif // __ANDROID_API__ >= 30
 
+#if __ANDROID_API__ >= 31
+
+/**
+ * Sets the intended frame rate for \a surface_control.
+ *
+ * On devices that are capable of running the display at different refresh rates, the system may
+ * choose a display refresh rate to better match this surface's frame rate. Usage of this API won't
+ * directly affect the application's frame production pipeline. However, because the system may
+ * change the display refresh rate, calls to this function may result in changes to Choreographer
+ * callback timings, and changes to the time interval at which the system releases buffers back to
+ * the application.
+ *
+ * \param frameRate is the intended frame rate of this surface, in frames per second. 0 is a special
+ * value that indicates the app will accept the system's choice for the display frame rate, which is
+ * the default behavior if this function isn't called. The frameRate param does <em>not</em> need to
+ * be a valid refresh rate for this device's display - e.g., it's fine to pass 30fps to a device
+ * that can only run the display at 60fps.
+ *
+ * \param compatibility The frame rate compatibility of this surface. The compatibility value may
+ * influence the system's choice of display frame rate. To specify a compatibility use the
+ * ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_* enum.
+ *
+ * \param shouldBeSeamless Whether display refresh rate transitions should be seamless. A
+ * seamless transition is one that doesn't have any visual interruptions, such as a black
+ * screen for a second or two. True indicates that any frame rate changes caused by this
+ * request should be seamless. False indicates that non-seamless refresh rates are also
+ * acceptable.
+ *
+ * Available since API level 31.
+ */
+void ASurfaceTransaction_setFrameRateWithSeamlessness(ASurfaceTransaction* transaction,
+                                      ASurfaceControl* surface_control, float frameRate,
+                                      int8_t compatibility, bool shouldBeSeamless)
+                                      __INTRODUCED_IN(31);
+
+#endif // __ANDROID_API__ >= 31
 __END_DECLS
 
 #endif // ANDROID_SURFACE_CONTROL_H
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 678613b..ac1c736 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -378,11 +378,11 @@
         }).detach();
     }
 
-    status_t setFrameRate(float frameRate, int8_t compatibility) override {
+    status_t setFrameRate(float frameRate, int8_t compatibility, bool shouldBeSeamless) override {
         if (!ValidateFrameRate(frameRate, compatibility, "BBQSurface::setFrameRate")) {
             return BAD_VALUE;
         }
-        return mBbq->setFrameRate(frameRate, compatibility);
+        return mBbq->setFrameRate(frameRate, compatibility, shouldBeSeamless);
     }
 
     status_t setFrameTimelineVsync(int64_t frameTimelineVsyncId) override {
@@ -392,12 +392,12 @@
 
 // TODO: Can we coalesce this with frame updates? Need to confirm
 // no timing issues.
-status_t BLASTBufferQueue::setFrameRate(float frameRate, int8_t compatibility) {
+status_t BLASTBufferQueue::setFrameRate(float frameRate, int8_t compatibility,
+                                        bool shouldBeSeamless) {
     std::unique_lock _lock{mMutex};
     SurfaceComposerClient::Transaction t;
 
-    return t.setFrameRate(mSurfaceControl, frameRate, compatibility)
-        .apply();
+    return t.setFrameRate(mSurfaceControl, frameRate, compatibility, shouldBeSeamless).apply();
 }
 
 status_t BLASTBufferQueue::setFrameTimelineVsync(int64_t frameTimelineVsyncId) {
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index 6f92233..a9fe5bf 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -1114,7 +1114,7 @@
     }
 
     virtual status_t setFrameRate(const sp<IGraphicBufferProducer>& surface, float frameRate,
-                                  int8_t compatibility) {
+                                  int8_t compatibility, bool shouldBeSeamless) {
         Parcel data, reply;
         status_t err = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
         if (err != NO_ERROR) {
@@ -1140,6 +1140,12 @@
             return err;
         }
 
+        err = data.writeBool(shouldBeSeamless);
+        if (err != NO_ERROR) {
+            ALOGE("setFrameRate: failed writing bool: %s (%d)", strerror(-err), -err);
+            return err;
+        }
+
         err = remote()->transact(BnSurfaceComposer::SET_FRAME_RATE, data, &reply);
         if (err != NO_ERROR) {
             ALOGE("setFrameRate: failed to transact: %s (%d)", strerror(-err), err);
@@ -2033,7 +2039,13 @@
                 ALOGE("setFrameRate: failed to read byte: %s (%d)", strerror(-err), -err);
                 return err;
             }
-            status_t result = setFrameRate(surface, frameRate, compatibility);
+            bool shouldBeSeamless;
+            err = data.readBool(&shouldBeSeamless);
+            if (err != NO_ERROR) {
+                ALOGE("setFrameRate: failed to read bool: %s (%d)", strerror(-err), -err);
+                return err;
+            }
+            status_t result = setFrameRate(surface, frameRate, compatibility, shouldBeSeamless);
             reply->writeInt32(result);
             return NO_ERROR;
         }
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 9722f36..90999fa 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -59,6 +59,7 @@
         frameRateSelectionPriority(-1),
         frameRate(0.0f),
         frameRateCompatibility(ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT),
+        shouldBeSeamless(true),
         fixedTransformHint(ui::Transform::ROT_INVALID),
         frameNumber(0) {
     matrix.dsdx = matrix.dtdy = 1.0f;
@@ -144,6 +145,7 @@
     SAFE_PARCEL(output.writeInt32, frameRateSelectionPriority);
     SAFE_PARCEL(output.writeFloat, frameRate);
     SAFE_PARCEL(output.writeByte, frameRateCompatibility);
+    SAFE_PARCEL(output.writeBool, shouldBeSeamless);
     SAFE_PARCEL(output.writeUint32, fixedTransformHint);
     SAFE_PARCEL(output.writeUint64, frameNumber);
     SAFE_PARCEL(output.writeInt64, frameTimelineVsyncId);
@@ -262,6 +264,7 @@
     SAFE_PARCEL(input.readInt32, &frameRateSelectionPriority);
     SAFE_PARCEL(input.readFloat, &frameRate);
     SAFE_PARCEL(input.readByte, &frameRateCompatibility);
+    SAFE_PARCEL(input.readBool, &shouldBeSeamless);
     SAFE_PARCEL(input.readUint32, &tmpUint32);
     fixedTransformHint = static_cast<ui::Transform::RotationFlags>(tmpUint32);
     SAFE_PARCEL(input.readUint64, &frameNumber);
@@ -521,6 +524,7 @@
         what |= eFrameRateChanged;
         frameRate = other.frameRate;
         frameRateCompatibility = other.frameRateCompatibility;
+        shouldBeSeamless = other.shouldBeSeamless;
     }
     if (other.what & eFixedTransformHintChanged) {
         what |= eFixedTransformHintChanged;
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index c1155ab..94390aa 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -1443,7 +1443,8 @@
 int Surface::dispatchSetFrameRate(va_list args) {
     float frameRate = static_cast<float>(va_arg(args, double));
     int8_t compatibility = static_cast<int8_t>(va_arg(args, int));
-    return setFrameRate(frameRate, compatibility);
+    bool shouldBeSeamless = static_cast<bool>(va_arg(args, int));
+    return setFrameRate(frameRate, compatibility, shouldBeSeamless);
 }
 
 int Surface::dispatchAddCancelInterceptor(va_list args) {
@@ -2279,7 +2280,7 @@
     mSurfaceListener->onBuffersDiscarded(discardedBufs);
 }
 
-status_t Surface::setFrameRate(float frameRate, int8_t compatibility) {
+status_t Surface::setFrameRate(float frameRate, int8_t compatibility, bool shouldBeSeamless) {
     ATRACE_CALL();
     ALOGV("Surface::setFrameRate");
 
@@ -2287,7 +2288,8 @@
         return BAD_VALUE;
     }
 
-    return composerService()->setFrameRate(mGraphicBufferProducer, frameRate, compatibility);
+    return composerService()->setFrameRate(mGraphicBufferProducer, frameRate, compatibility,
+                                           shouldBeSeamless);
 }
 
 status_t Surface::setFrameTimelineVsync(int64_t frameTimelineVsyncId) {
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 039e900..a822598 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -1474,7 +1474,8 @@
 }
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFrameRate(
-        const sp<SurfaceControl>& sc, float frameRate, int8_t compatibility) {
+        const sp<SurfaceControl>& sc, float frameRate, int8_t compatibility,
+        bool shouldBeSeamless) {
     layer_state_t* s = getLayerState(sc);
     if (!s) {
         mStatus = BAD_INDEX;
@@ -1487,6 +1488,7 @@
     s->what |= layer_state_t::eFrameRateChanged;
     s->frameRate = frameRate;
     s->frameRateCompatibility = compatibility;
+    s->shouldBeSeamless = shouldBeSeamless;
     return *this;
 }
 
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 2300e81..7741d8c 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -85,7 +85,7 @@
     void update(const sp<SurfaceControl>& surface, uint32_t width, uint32_t height);
     void flushShadowQueue() { mFlushShadowQueue = true; }
 
-    status_t setFrameRate(float frameRate, int8_t compatibility);
+    status_t setFrameRate(float frameRate, int8_t compatibility, bool shouldBeSeamless);
     status_t setFrameTimelineVsync(int64_t frameTimelineVsyncId);
 
     virtual ~BLASTBufferQueue() = default;
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 5cd9356..9e96b79 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -475,7 +475,7 @@
      * Sets the intended frame rate for a surface. See ANativeWindow_setFrameRate() for more info.
      */
     virtual status_t setFrameRate(const sp<IGraphicBufferProducer>& surface, float frameRate,
-                                  int8_t compatibility) = 0;
+                                  int8_t compatibility, bool shouldBeSeamless) = 0;
 
     /*
      * Acquire a frame rate flexibility token from SurfaceFlinger. While this token is acquired,
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index a73d9a6..d9f2806 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -218,6 +218,7 @@
     // Layer frame rate and compatibility. See ANativeWindow_setFrameRate().
     float frameRate;
     int8_t frameRateCompatibility;
+    bool shouldBeSeamless;
 
     // Set by window manager indicating the layer and all its children are
     // in a different orientation than the display. The hint suggests that
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index 4aa076e..82bc5c9 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -186,7 +186,7 @@
     status_t getUniqueId(uint64_t* outId) const;
     status_t getConsumerUsage(uint64_t* outUsage) const;
 
-    virtual status_t setFrameRate(float frameRate, int8_t compatibility);
+    virtual status_t setFrameRate(float frameRate, int8_t compatibility, bool shouldBeSeamless);
     virtual status_t setFrameTimelineVsync(int64_t frameTimelineVsyncId);
 
 protected:
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 73909a3..6289c6a 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -524,7 +524,7 @@
         Transaction& setShadowRadius(const sp<SurfaceControl>& sc, float cornerRadius);
 
         Transaction& setFrameRate(const sp<SurfaceControl>& sc, float frameRate,
-                                  int8_t compatibility);
+                                  int8_t compatibility, bool shouldBeSeamless);
 
         // Set by window manager indicating the layer and all its children are
         // in a different orientation than the display. The hint suggests that
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 0cd3962..2392ae5 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -869,7 +869,7 @@
     }
 
     status_t setFrameRate(const sp<IGraphicBufferProducer>& /*surface*/, float /*frameRate*/,
-                          int8_t /*compatibility*/) override {
+                          int8_t /*compatibility*/, bool /*shouldBeSeamless*/) override {
         return NO_ERROR;
     }
 
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index fd1793b..b406a9c 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -159,10 +159,8 @@
 }
 
 int32_t ANativeWindow_setFrameRate(ANativeWindow* window, float frameRate, int8_t compatibility) {
-    if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) {
-        return -EINVAL;
-    }
-    return native_window_set_frame_rate(window, frameRate, compatibility);
+    return ANativeWindow_setFrameRateWithSeamlessness(window, frameRate, compatibility,
+        /*shouldBeSeamless*/ true);
 }
 
 void ANativeWindow_tryAllocateBuffers(ANativeWindow* window) {
@@ -172,6 +170,13 @@
     window->perform(window, NATIVE_WINDOW_ALLOCATE_BUFFERS);
 }
 
+int32_t ANativeWindow_setFrameRateWithSeamlessness(ANativeWindow* window, float frameRate,
+        int8_t compatibility, bool shouldBeSeamless) {
+    if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) {
+        return -EINVAL;
+    }
+    return native_window_set_frame_rate(window, frameRate, compatibility, shouldBeSeamless);
+}
 
 /**************************************************************************************************
  * vndk-stable
diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h
index 36aad2e..deea59b 100644
--- a/libs/nativewindow/include/android/native_window.h
+++ b/libs/nativewindow/include/android/native_window.h
@@ -34,6 +34,7 @@
 #define ANDROID_NATIVE_WINDOW_H
 
 #include <stdint.h>
+#include <stdbool.h>
 #include <sys/cdefs.h>
 
 #include <android/data_space.h>
@@ -256,36 +257,11 @@
 };
 
 /**
- * Sets the intended frame rate for this window.
+ * Same as ANativeWindow_setFrameRateWithSeamlessness(window, frameRate, compatibility, true).
  *
- * On devices that are capable of running the display at different refresh
- * rates, the system may choose a display refresh rate to better match this
- * window's frame rate. Usage of this API won't introduce frame rate throttling,
- * or affect other aspects of the application's frame production
- * pipeline. However, because the system may change the display refresh rate,
- * calls to this function may result in changes to Choreographer callback
- * timings, and changes to the time interval at which the system releases
- * buffers back to the application.
- *
- * Note that this only has an effect for windows presented on the display. If
- * this ANativeWindow is consumed by something other than the system compositor,
- * e.g. a media codec, this call has no effect.
+ * See ANativeWindow_setFrameRateWithSeamlessness().
  *
  * Available since API level 30.
- *
- * \param frameRate The intended frame rate of this window, in frames per
- * second. 0 is a special value that indicates the app will accept the system's
- * choice for the display frame rate, which is the default behavior if this
- * function isn't called. The frameRate param does <em>not</em> need to be a
- * valid refresh rate for this device's display - e.g., it's fine to pass 30fps
- * to a device that can only run the display at 60fps.
- *
- * \param compatibility The frame rate compatibility of this window. The
- * compatibility value may influence the system's choice of display refresh
- * rate. See the ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_* values for more info.
- *
- * \return 0 for success, -EINVAL if the window, frame rate, or compatibility
- * value are invalid.
  */
 int32_t ANativeWindow_setFrameRate(ANativeWindow* window, float frameRate, int8_t compatibility)
         __INTRODUCED_IN(30);
@@ -303,6 +279,51 @@
 
 #endif // __ANDROID_API__ >= 30
 
+#if __ANDROID_API__ >= 31
+
+/**
+ * Sets the intended frame rate for this window.
+ *
+ * On devices that are capable of running the display at different refresh
+ * rates, the system may choose a display refresh rate to better match this
+ * window's frame rate. Usage of this API won't introduce frame rate throttling,
+ * or affect other aspects of the application's frame production
+ * pipeline. However, because the system may change the display refresh rate,
+ * calls to this function may result in changes to Choreographer callback
+ * timings, and changes to the time interval at which the system releases
+ * buffers back to the application.
+ *
+ * Note that this only has an effect for windows presented on the display. If
+ * this ANativeWindow is consumed by something other than the system compositor,
+ * e.g. a media codec, this call has no effect.
+ *
+ * Available since API level 31.
+ *
+ * \param frameRate The intended frame rate of this window, in frames per
+ * second. 0 is a special value that indicates the app will accept the system's
+ * choice for the display frame rate, which is the default behavior if this
+ * function isn't called. The frameRate param does <em>not</em> need to be a
+ * valid refresh rate for this device's display - e.g., it's fine to pass 30fps
+ * to a device that can only run the display at 60fps.
+ *
+ * \param compatibility The frame rate compatibility of this window. The
+ * compatibility value may influence the system's choice of display refresh
+ * rate. See the ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_* values for more info.
+ *
+ * \param shouldBeSeamless Whether display refresh rate transitions should be seamless. A
+ * seamless transition is one that doesn't have any visual interruptions, such as a black
+ * screen for a second or two. True indicates that any frame rate changes caused by this
+ * request should be seamless. False indicates that non-seamless refresh rates are also
+ * acceptable.
+ *
+ * \return 0 for success, -EINVAL if the window, frame rate, or compatibility
+ * value are invalid.
+ */
+int32_t ANativeWindow_setFrameRateWithSeamlessness(ANativeWindow* window, float frameRate,
+        int8_t compatibility, bool shouldBeSeamless) __INTRODUCED_IN(31);
+
+#endif // __ANDROID_API__ >= 31
+
 #ifdef __cplusplus
 };
 #endif
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index 138e08f..82d2e66 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1018,9 +1018,9 @@
 }
 
 static inline int native_window_set_frame_rate(struct ANativeWindow* window, float frameRate,
-                                               int8_t compatibility) {
+                                        int8_t compatibility, bool shouldBeSeamless) {
     return window->perform(window, NATIVE_WINDOW_SET_FRAME_RATE, (double)frameRate,
-                           (int)compatibility);
+                           (int)compatibility, (int)shouldBeSeamless);
 }
 
 static inline int native_window_set_frame_timeline_vsync(struct ANativeWindow* window,
diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt
index 1b5d20d..de48ec2 100644
--- a/libs/nativewindow/libnativewindow.map.txt
+++ b/libs/nativewindow/libnativewindow.map.txt
@@ -46,6 +46,7 @@
     ANativeWindow_setBuffersTransform;
     ANativeWindow_setDequeueTimeout; # apex # introduced=30
     ANativeWindow_setFrameRate; # introduced=30
+    ANativeWindow_setFrameRateWithSeamlessness; # introduced=31
     ANativeWindow_setSharedBufferMode; # llndk
     ANativeWindow_setSwapInterval; # llndk
     ANativeWindow_setUsage; # llndk
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