Merge "Improve dump: type & touchOcclusionMode"
diff --git a/include/android/surface_control.h b/include/android/surface_control.h
index cbcf6ec..7a74248 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -48,7 +48,7 @@
 
 /**
  * Creates an ASurfaceControl with either ANativeWindow or an ASurfaceControl as its parent.
- * |debug_name| is a debug name associated with this surface. It can be used to
+ * \a debug_name is a debug name associated with this surface. It can be used to
  * identify this surface in the SurfaceFlinger's layer tree. It must not be
  * null.
  *
@@ -69,7 +69,7 @@
                                         __INTRODUCED_IN(29);
 
 /**
- * Releases the |surface_control| object. After releasing the ASurfaceControl the caller no longer
+ * Releases the \a surface_control object. After releasing the ASurfaceControl the caller no longer
  * has ownership of the AsurfaceControl. The surface and it's children may remain on display as long
  * as their parent remains on display.
  *
@@ -87,21 +87,21 @@
 
 /**
  * The caller takes ownership of the transaction and must release it using
- * ASurfaceControl_delete below.
+ * ASurfaceTransaction_delete() below.
  *
  * Available since API level 29.
  */
 ASurfaceTransaction* ASurfaceTransaction_create() __INTRODUCED_IN(29);
 
 /**
- * Destroys the |transaction| object.
+ * Destroys the \a transaction object.
  *
  * Available since API level 29.
  */
 void ASurfaceTransaction_delete(ASurfaceTransaction* transaction) __INTRODUCED_IN(29);
 
 /**
- * Applies the updates accumulated in |transaction|.
+ * Applies the updates accumulated in \a transaction.
  *
  * Note that the transaction is guaranteed to be applied atomically. The
  * transactions which are applied on the same thread are also guaranteed to be
@@ -123,10 +123,10 @@
  * ASurfaceTransaction_OnComplete callback can be used to be notified when a frame
  * including the updates in a transaction was presented.
  *
- * |context| is the optional context provided by the client that is passed into
+ * \param context Optional context provided by the client that is passed into
  * the callback.
  *
- * |stats| is an opaque handle that can be passed to ASurfaceTransactionStats functions to query
+ * \param stats Opaque handle that can be passed to ASurfaceTransactionStats functions to query
  * information about the transaction. The handle is only valid during the callback.
  *
  * THREADING
@@ -157,14 +157,14 @@
                                                __INTRODUCED_IN(29);
 
 /**
- * |outASurfaceControls| returns an array of ASurfaceControl pointers that were updated during the
+ * \a outASurfaceControls returns an array of ASurfaceControl pointers that were updated during the
  * transaction. Stats for the surfaces can be queried through ASurfaceTransactionStats functions.
  * When the client is done using the array, it must release it by calling
  * ASurfaceTransactionStats_releaseASurfaceControls.
  *
  * Available since API level 29.
  *
- * |outASurfaceControlsSize| returns the size of the ASurfaceControls array.
+ * \a outASurfaceControlsSize returns the size of the ASurfaceControls array.
  */
 void ASurfaceTransactionStats_getASurfaceControls(ASurfaceTransactionStats* surface_transaction_stats,
                                                   ASurfaceControl*** outASurfaceControls,
@@ -172,7 +172,7 @@
                                                   __INTRODUCED_IN(29);
 /**
  * Releases the array of ASurfaceControls that were returned by
- * ASurfaceTransactionStats_getASurfaceControls.
+ * ASurfaceTransactionStats_getASurfaceControls().
  *
  * Available since API level 29.
  */
@@ -197,8 +197,8 @@
  * buffer is already released. The recipient of the callback takes ownership of the
  * previousReleaseFenceFd and is responsible for closing it.
  *
- * Each time a buffer is set through ASurfaceTransaction_setBuffer()/_setCachedBuffer() on a
- * transaction which is applied, the framework takes a ref on this buffer. The framework treats the
+ * Each time a buffer is set through ASurfaceTransaction_setBuffer() on a transaction
+ * which is applied, the framework takes a ref on this buffer. The framework treats the
  * addition of a buffer to a particular surface as a unique ref. When a transaction updates or
  * removes a buffer from a surface, or removes the surface itself from the tree, this ref is
  * guaranteed to be released in the OnComplete callback for this transaction. The
@@ -226,10 +226,10 @@
                                        ASurfaceTransaction_OnComplete func) __INTRODUCED_IN(29);
 
 /**
- * Reparents the |surface_control| from its old parent to the |new_parent| surface control.
- * Any children of the* reparented |surface_control| will remain children of the |surface_control|.
+ * Reparents the \a surface_control from its old parent to the \a new_parent surface control.
+ * Any children of the reparented \a surface_control will remain children of the \a surface_control.
  *
- * The |new_parent| can be null. Surface controls with a null parent do not appear on the display.
+ * The \a new_parent can be null. Surface controls with a null parent do not appear on the display.
  *
  * Available since API level 29.
  */
@@ -237,14 +237,16 @@
                                   ASurfaceControl* surface_control, ASurfaceControl* new_parent)
                                   __INTRODUCED_IN(29);
 
-/* Parameter for ASurfaceTransaction_setVisibility */
+/**
+ * Parameter for ASurfaceTransaction_setVisibility().
+ */
 enum {
     ASURFACE_TRANSACTION_VISIBILITY_HIDE = 0,
     ASURFACE_TRANSACTION_VISIBILITY_SHOW = 1,
 };
 /**
- * Updates the visibility of |surface_control|. If show is set to
- * ASURFACE_TRANSACTION_VISIBILITY_HIDE, the |surface_control| and all surfaces in its subtree will
+ * Updates the visibility of \a surface_control. If show is set to
+ * ASURFACE_TRANSACTION_VISIBILITY_HIDE, the \a surface_control and all surfaces in its subtree will
  * be hidden.
  *
  * Available since API level 29.
@@ -254,7 +256,7 @@
                                        __INTRODUCED_IN(29);
 
 /**
- * Updates the z order index for |surface_control|. Note that the z order for a surface
+ * Updates the z order index for \a surface_control. Note that the z order for a surface
  * is relative to other surfaces which are siblings of this surface. The behavior of sibilings with
  * the same z order is undefined.
  *
@@ -267,11 +269,11 @@
                                    __INTRODUCED_IN(29);
 
 /**
- * Updates the AHardwareBuffer displayed for |surface_control|. If not -1, the
+ * Updates the AHardwareBuffer displayed for \a surface_control. If not -1, the
  * acquire_fence_fd should be a file descriptor that is signaled when all pending work
  * for the buffer is complete and the buffer can be safely read.
  *
- * The frameworks takes ownership of the |acquire_fence_fd| passed and is responsible
+ * The frameworks takes ownership of the \a acquire_fence_fd passed and is responsible
  * for closing it.
  *
  * Available since API level 29.
@@ -281,9 +283,9 @@
                                    int acquire_fence_fd = -1) __INTRODUCED_IN(29);
 
 /**
- * Updates the color for |surface_control|.  This will make the background color for the
- * ASurfaceControl visible in transparent regions of the surface.  Colors |r|, |g|,
- * and |b| must be within the range that is valid for |dataspace|.  |dataspace| and |alpha|
+ * Updates the color for \a surface_control.  This will make the background color for the
+ * ASurfaceControl visible in transparent regions of the surface.  Colors \a r, \a g,
+ * and \a b must be within the range that is valid for \a dataspace.  \a dataspace and \a alpha
  * will be the dataspace and alpha set for the background color layer.
  *
  * Available since API level 29.
@@ -294,15 +296,15 @@
                                   __INTRODUCED_IN(29);
 
 /**
- * |source| the sub-rect within the buffer's content to be rendered inside the surface's area
+ * \param source The sub-rect within the buffer's content to be rendered inside the surface's area
  * The surface's source rect is clipped by the bounds of its current buffer. The source rect's width
  * and height must be > 0.
  *
- * |destination| specifies the rect in the parent's space where this surface will be drawn. The post
+ * \param destination Specifies the rect in the parent's space where this surface will be drawn. The post
  * source rect bounds are scaled to fit the destination rect. The surface's destination rect is
  * clipped by the bounds of its parent. The destination rect's width and height must be > 0.
  *
- * |transform| the transform applied after the source rect is applied to the buffer. This parameter
+ * \param transform The transform applied after the source rect is applied to the buffer. This parameter
  * should be set to 0 for no transform. To specify a transfrom use the NATIVE_WINDOW_TRANSFORM_*
  * enum.
  *
@@ -314,7 +316,9 @@
                                      __INTRODUCED_IN(29);
 
 
-/* Parameter for ASurfaceTransaction_setBufferTransparency */
+/**
+ * Parameter for ASurfaceTransaction_setBufferTransparency().
+ */
 enum {
     ASURFACE_TRANSACTION_TRANSPARENCY_TRANSPARENT = 0,
     ASURFACE_TRANSACTION_TRANSPARENCY_TRANSLUCENT = 1,
@@ -360,7 +364,7 @@
 /**
  * Sets the alpha for the buffer. It uses a premultiplied blending.
  *
- * The |alpha| must be between 0.0 and 1.0.
+ * The \a alpha must be between 0.0 and 1.0.
  *
  * Available since API level 29.
  */
@@ -379,10 +383,10 @@
                                             ASurfaceControl* surface_control, ADataSpace data_space)
                                             __INTRODUCED_IN(29);
 
-/*
+/**
  * SMPTE ST 2086 "Mastering Display Color Volume" static metadata
  *
- * When |metadata| is set to null, the framework does not use any smpte2086 metadata when rendering
+ * When \a metadata is set to null, the framework does not use any smpte2086 metadata when rendering
  * the surface's buffer.
  *
  * Available since API level 29.
@@ -392,10 +396,10 @@
                                                   struct AHdrMetadata_smpte2086* metadata)
                                                   __INTRODUCED_IN(29);
 
-/*
+/**
  * Sets the CTA 861.3 "HDR Static Metadata Extension" static metadata on a surface.
  *
- * When |metadata| is set to null, the framework does not use any cta861.3 metadata when rendering
+ * When \a metadata is set to null, the framework does not use any cta861.3 metadata when rendering
  * the surface's buffer.
  *
  * Available since API level 29.
@@ -410,24 +414,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 +427,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/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index fff7854..8c5f0e6 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -252,8 +252,8 @@
 
     // initialize the renderer while GL is current
     std::unique_ptr<SkiaGLRenderEngine> engine =
-            std::make_unique<SkiaGLRenderEngine>(args, display, config, ctxt, placeholder,
-                                                 protectedContext, protectedPlaceholder);
+            std::make_unique<SkiaGLRenderEngine>(args, display, ctxt, placeholder, protectedContext,
+                                                 protectedPlaceholder);
 
     ALOGI("OpenGL ES informations:");
     ALOGI("vendor    : %s", extensions.getVendor());
@@ -306,38 +306,52 @@
 }
 
 SkiaGLRenderEngine::SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display,
-                                       EGLConfig config, EGLContext ctxt, EGLSurface placeholder,
+                                       EGLContext ctxt, EGLSurface placeholder,
                                        EGLContext protectedContext, EGLSurface protectedPlaceholder)
       : mEGLDisplay(display),
-        mEGLConfig(config),
         mEGLContext(ctxt),
         mPlaceholderSurface(placeholder),
         mProtectedEGLContext(protectedContext),
         mProtectedPlaceholderSurface(protectedPlaceholder),
         mUseColorManagement(args.useColorManagement) {
-    // Suppress unused field warnings for things we definitely will need/use
-    // These EGL fields will all be needed for toggling between protected & unprotected contexts
-    // Or we need different RE instances for that
-    (void)mEGLDisplay;
-    (void)mEGLConfig;
-    (void)mEGLContext;
-    (void)mPlaceholderSurface;
-    (void)mProtectedEGLContext;
-    (void)mProtectedPlaceholderSurface;
-
     sk_sp<const GrGLInterface> glInterface(GrGLCreateNativeInterface());
     LOG_ALWAYS_FATAL_IF(!glInterface.get());
 
     GrContextOptions options;
     options.fPreferExternalImagesOverES3 = true;
     options.fDisableDistanceFieldPaths = true;
-    mGrContext = GrDirectContext::MakeGL(std::move(glInterface), options);
+    mGrContext = GrDirectContext::MakeGL(glInterface, options);
+    if (useProtectedContext(true)) {
+        mProtectedGrContext = GrDirectContext::MakeGL(glInterface, options);
+        useProtectedContext(false);
+    }
 
     if (args.supportsBackgroundBlur) {
         mBlurFilter = new BlurFilter();
     }
 }
 
+bool SkiaGLRenderEngine::supportsProtectedContent() const {
+    return mProtectedEGLContext != EGL_NO_CONTEXT;
+}
+
+bool SkiaGLRenderEngine::useProtectedContext(bool useProtectedContext) {
+    if (useProtectedContext == mInProtectedContext) {
+        return true;
+    }
+    if (useProtectedContext && supportsProtectedContent()) {
+        return false;
+    }
+    const EGLSurface surface =
+            useProtectedContext ? mProtectedPlaceholderSurface : mPlaceholderSurface;
+    const EGLContext context = useProtectedContext ? mProtectedEGLContext : mEGLContext;
+    const bool success = eglMakeCurrent(mEGLDisplay, surface, surface, context) == EGL_TRUE;
+    if (success) {
+        mInProtectedContext = useProtectedContext;
+    }
+    return success;
+}
+
 base::unique_fd SkiaGLRenderEngine::flush() {
     ATRACE_CALL();
     if (!gl::GLExtensions::getInstance().hasNativeFenceSync()) {
@@ -471,22 +485,23 @@
         return BAD_VALUE;
     }
 
+    auto grContext = mInProtectedContext ? mProtectedGrContext : mGrContext;
+    auto cache = mInProtectedContext ? mProtectedSurfaceCache : mSurfaceCache;
     AHardwareBuffer_Desc bufferDesc;
     AHardwareBuffer_describe(buffer->toAHardwareBuffer(), &bufferDesc);
-
     LOG_ALWAYS_FATAL_IF(!hasUsage(bufferDesc, AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE),
                         "missing usage");
 
     sk_sp<SkSurface> surface;
     if (useFramebufferCache) {
-        auto iter = mSurfaceCache.find(buffer->getId());
-        if (iter != mSurfaceCache.end()) {
+        auto iter = cache.find(buffer->getId());
+        if (iter != cache.end()) {
             ALOGV("Cache hit!");
             surface = iter->second;
         }
     }
     if (!surface) {
-        surface = SkSurface::MakeFromAHardwareBuffer(mGrContext.get(), buffer->toAHardwareBuffer(),
+        surface = SkSurface::MakeFromAHardwareBuffer(grContext.get(), buffer->toAHardwareBuffer(),
                                                      GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
                                                      mUseColorManagement
                                                              ? toColorSpace(display.outputDataspace)
@@ -494,7 +509,7 @@
                                                      nullptr);
         if (useFramebufferCache && surface) {
             ALOGD("Adding to cache");
-            mSurfaceCache.insert({buffer->getId(), surface});
+            cache.insert({buffer->getId(), surface});
         }
     }
     if (!surface) {
@@ -705,7 +720,7 @@
     } else {
         ATRACE_BEGIN("Submit(sync=false)");
     }
-    bool success = mGrContext->submit(requireSync);
+    bool success = grContext->submit(requireSync);
     ATRACE_END();
     if (!success) {
         ALOGE("Failed to flush RenderEngine commands");
@@ -897,6 +912,7 @@
 
 void SkiaGLRenderEngine::cleanFramebufferCache() {
     mSurfaceCache.clear();
+    mProtectedSurfaceCache.clear();
 }
 
 } // namespace skia
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index 43db3b1..965cb41 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -40,8 +40,8 @@
 class SkiaGLRenderEngine : public skia::SkiaRenderEngine {
 public:
     static std::unique_ptr<SkiaGLRenderEngine> create(const RenderEngineCreationArgs& args);
-    SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display, EGLConfig config,
-                       EGLContext ctxt, EGLSurface placeholder, EGLContext protectedContext,
+    SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display, EGLContext ctxt,
+                       EGLSurface placeholder, EGLContext protectedContext,
                        EGLSurface protectedPlaceholder);
     ~SkiaGLRenderEngine() override{};
 
@@ -51,6 +51,9 @@
                         const sp<GraphicBuffer>& buffer, const bool useFramebufferCache,
                         base::unique_fd&& bufferFence, base::unique_fd* drawFence) override;
     void cleanFramebufferCache() override;
+    bool isProtected() const override { return mInProtectedContext; }
+    bool supportsProtectedContent() const override;
+    bool useProtectedContext(bool useProtectedContext) override;
 
 protected:
     void dump(std::string& /*result*/) override{};
@@ -81,7 +84,6 @@
                         const SkMatrix& drawTransform, sk_sp<SkSurface> blurrendSurface);
 
     EGLDisplay mEGLDisplay;
-    EGLConfig mEGLConfig;
     EGLContext mEGLContext;
     EGLSurface mPlaceholderSurface;
     EGLContext mProtectedEGLContext;
@@ -100,9 +102,14 @@
 
     sp<Fence> mLastDrawFence;
 
+    // Graphics context used for creating surfaces and submitting commands
     sk_sp<GrDirectContext> mGrContext;
+    // Same as above, but for protected content (eg. DRM)
+    sk_sp<GrDirectContext> mProtectedGrContext;
 
     std::unordered_map<uint64_t, sk_sp<SkSurface>> mSurfaceCache;
+    std::unordered_map<uint64_t, sk_sp<SkSurface>> mProtectedSurfaceCache;
+    bool mInProtectedContext = false;
 };
 
 } // namespace skia
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 1911a0a..b6b754b 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..b872d7a 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();
@@ -660,4 +719,26 @@
     return static_cast<int>(numPeriods);
 }
 
+void RefreshRateConfigs::dump(std::string& result) const {
+    std::lock_guard lock(mLock);
+    base::StringAppendF(&result, "DesiredDisplayConfigSpecs (DisplayManager): %s\n\n",
+                        mDisplayManagerPolicy.toString().c_str());
+    scheduler::RefreshRateConfigs::Policy currentPolicy = *getCurrentPolicyLocked();
+    if (mOverridePolicy && currentPolicy != mDisplayManagerPolicy) {
+        base::StringAppendF(&result, "DesiredDisplayConfigSpecs (Override): %s\n\n",
+                            currentPolicy.toString().c_str());
+    }
+
+    auto config = mCurrentRefreshRate->hwcConfig;
+    base::StringAppendF(&result, "Current config: %s\n", mCurrentRefreshRate->toString().c_str());
+
+    result.append("Refresh rates:\n");
+    for (const auto& [id, refreshRate] : mRefreshRates) {
+        config = refreshRate->hwcConfig;
+        base::StringAppendF(&result, "\t%s\n", refreshRate->toString().c_str());
+    }
+
+    result.append("\n");
+}
+
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 8ff92a0..3159352 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;
@@ -318,6 +322,8 @@
     // Returns a divider for the current refresh rate
     int getRefreshRateDividerForUid(uid_t) const EXCLUDES(mLock);
 
+    void dump(std::string& result) const EXCLUDES(mLock);
+
 private:
     friend class RefreshRateConfigsTest;
 
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 27facd6..91f050c 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;
         }
     }
@@ -4373,16 +4374,10 @@
                   "      present offset: %9" PRId64 " ns\t     VSYNC period: %9" PRId64 " ns\n\n",
                   dispSyncPresentTimeOffset, getVsyncPeriodFromHWC());
 
-    scheduler::RefreshRateConfigs::Policy policy = mRefreshRateConfigs->getDisplayManagerPolicy();
-    StringAppendF(&result, "DesiredDisplayConfigSpecs (DisplayManager): %s\n\n",
-                  policy.toString().c_str());
+    mRefreshRateConfigs->dump(result);
+
     StringAppendF(&result, "(config override by backdoor: %s)\n\n",
                   mDebugDisplayConfigSetByBackdoor ? "yes" : "no");
-    scheduler::RefreshRateConfigs::Policy currentPolicy = mRefreshRateConfigs->getCurrentPolicy();
-    if (currentPolicy != policy) {
-        StringAppendF(&result, "DesiredDisplayConfigSpecs (Override): %s\n\n",
-                      currentPolicy.toString().c_str());
-    }
 
     mScheduler->dump(mAppConnectionHandle, result);
     mScheduler->dumpVsync(result);
@@ -6141,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;
     }
@@ -6154,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
diff --git a/services/vr/bufferhubd/Android.bp b/services/vr/bufferhubd/Android.bp
index afb3004..7097e7a 100644
--- a/services/vr/bufferhubd/Android.bp
+++ b/services/vr/bufferhubd/Android.bp
@@ -15,7 +15,6 @@
 sharedLibraries = [
     "libbase",
     "libcutils",
-    "libgtest_prod",
     "libgui",
     "liblog",
     "libpdx_default_transport",
@@ -48,6 +47,7 @@
 
 cc_binary {
     srcs: ["bufferhubd.cpp"],
+    system_ext_specific: true,
     cflags: [
         "-DLOG_TAG=\"bufferhubd\"",
         "-DTRACE=0",