Merge "Check for buffer changes explicitly instead of relying on acquire fence changes" into sc-dev
diff --git a/cmds/installd/run_dex2oat.cpp b/cmds/installd/run_dex2oat.cpp
index 17ea903..a27fd10 100644
--- a/cmds/installd/run_dex2oat.cpp
+++ b/cmds/installd/run_dex2oat.cpp
@@ -86,7 +86,7 @@
                             bool generate_compact_dex,
                             bool use_jitzygote_image,
                             const char* compilation_reason) {
-    PrepareBootImageAndBootClasspathFlags(use_jitzygote_image);
+    PrepareBootImageFlags(use_jitzygote_image);
 
     PrepareInputFileFlags(output_oat, output_vdex, output_image, input_dex, input_vdex,
                           dex_metadata, profile, swap_fd, class_loader_context,
@@ -112,7 +112,7 @@
 
 RunDex2Oat::~RunDex2Oat() {}
 
-void RunDex2Oat::PrepareBootImageAndBootClasspathFlags(bool use_jitzygote_image) {
+void RunDex2Oat::PrepareBootImageFlags(bool use_jitzygote_image) {
     std::string boot_image;
     if (use_jitzygote_image) {
         boot_image = StringPrintf("--boot-image=%s", kJitZygoteImage);
@@ -120,23 +120,6 @@
         boot_image = MapPropertyToArg("dalvik.vm.boot-image", "--boot-image=%s");
     }
     AddArg(boot_image);
-
-    // If DEX2OATBOOTCLASSPATH is not in the environment, dex2oat is going to query
-    // BOOTCLASSPATH.
-    char* dex2oat_bootclasspath = getenv("DEX2OATBOOTCLASSPATH");
-    if (dex2oat_bootclasspath != nullptr) {
-        AddRuntimeArg(StringPrintf("-Xbootclasspath:%s", dex2oat_bootclasspath));
-    }
-
-    std::string updatable_bcp_packages =
-            MapPropertyToArg("dalvik.vm.dex2oat-updatable-bcp-packages-file",
-                             "--updatable-bcp-packages-file=%s");
-    if (updatable_bcp_packages.empty()) {
-        // Make dex2oat fail by providing non-existent file name.
-        updatable_bcp_packages =
-                "--updatable-bcp-packages-file=/nonx/updatable-bcp-packages.txt";
-    }
-    AddArg(updatable_bcp_packages);
 }
 
 void RunDex2Oat::PrepareInputFileFlags(const UniqueFile& output_oat,
diff --git a/cmds/installd/run_dex2oat.h b/cmds/installd/run_dex2oat.h
index 325a3a2..475e124 100644
--- a/cmds/installd/run_dex2oat.h
+++ b/cmds/installd/run_dex2oat.h
@@ -56,7 +56,7 @@
     void Exec(int exit_code);
 
   protected:
-    void PrepareBootImageAndBootClasspathFlags(bool use_jitzygote_image);
+    void PrepareBootImageFlags(bool use_jitzygote_image);
     void PrepareInputFileFlags(const UniqueFile& output_oat,
                                const UniqueFile& output_vdex,
                                const UniqueFile& output_image,
diff --git a/cmds/installd/run_dex2oat_test.cpp b/cmds/installd/run_dex2oat_test.cpp
index 3813cf7..0a638cd 100644
--- a/cmds/installd/run_dex2oat_test.cpp
+++ b/cmds/installd/run_dex2oat_test.cpp
@@ -175,8 +175,6 @@
         default_expected_flags_["--swap-fd"] = FLAG_UNUSED;
         default_expected_flags_["--class-loader-context"] = FLAG_UNUSED;
         default_expected_flags_["--class-loader-context-fds"] = FLAG_UNUSED;
-        default_expected_flags_["--updatable-bcp-packages-file"] =
-                "=/nonx/updatable-bcp-packages.txt";
 
         // Arch
         default_expected_flags_["--instruction-set"] = "=arm64";
@@ -320,28 +318,6 @@
     VerifyExpectedFlags();
 }
 
-TEST_F(RunDex2OatTest, DEX2OATBOOTCLASSPATH) {
-    ASSERT_EQ(nullptr, getenv("DEX2OATBOOTCLASSPATH"));
-    ASSERT_EQ(0, setenv("DEX2OATBOOTCLASSPATH", "foobar", /*override=*/ false))
-        << "Failed to setenv: " << strerror(errno);
-
-    CallRunDex2Oat(RunDex2OatArgs::MakeDefaultTestArgs());
-
-    SetExpectedFlagUsed("-Xbootclasspath", ":foobar");
-    VerifyExpectedFlags();
-
-    ASSERT_EQ(0, unsetenv("DEX2OATBOOTCLASSPATH"))
-        << "Failed to setenv: " << strerror(errno);
-}
-
-TEST_F(RunDex2OatTest, UpdatableBootClassPath) {
-    setSystemProperty("dalvik.vm.dex2oat-updatable-bcp-packages-file", "/path/to/file");
-    CallRunDex2Oat(RunDex2OatArgs::MakeDefaultTestArgs());
-
-    SetExpectedFlagUsed("--updatable-bcp-packages-file", "=/path/to/file");
-    VerifyExpectedFlags();
-}
-
 TEST_F(RunDex2OatTest, DoNotGenerateCompactDex) {
     auto args = RunDex2OatArgs::MakeDefaultTestArgs();
     args->generate_compact_dex = false;
diff --git a/include/android/surface_control.h b/include/android/surface_control.h
index 91726cd..ee75957 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -455,10 +455,10 @@
                                                  __INTRODUCED_IN(29);
 
 /**
- * Same as ASurfaceTransaction_setFrameRateWithSeamlessness(transaction, surface_control,
- * frameRate, compatibility, true).
+ * Same as ASurfaceTransaction_setFrameRateWithChangeStrategy(transaction, surface_control,
+ * frameRate, compatibility, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS).
  *
- * See ASurfaceTransaction_setFrameRateWithSeamlessness().
+ * See ASurfaceTransaction_setFrameRateWithChangeStrategy().
  *
  * Available since API level 30.
  */
@@ -486,17 +486,15 @@
  * 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.
+ * \param changeFrameRateStrategy 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. See the ANATIVEWINDOW_CHANGE_FRAME_RATE_* values.
  *
  * Available since API level 31.
  */
-void ASurfaceTransaction_setFrameRateWithSeamlessness(ASurfaceTransaction* transaction,
+void ASurfaceTransaction_setFrameRateWithChangeStrategy(ASurfaceTransaction* transaction,
                                       ASurfaceControl* surface_control, float frameRate,
-                                      int8_t compatibility, bool shouldBeSeamless)
+                                      int8_t compatibility, int8_t changeFrameRateStrategy)
                                       __INTRODUCED_IN(31);
 
 __END_DECLS
diff --git a/include/input/Input.h b/include/input/Input.h
index f9fe6b9..bb5ca0e 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -266,6 +266,20 @@
 const char* motionClassificationToString(MotionClassification classification);
 
 /**
+ * Portion of FrameMetrics timeline of interest to input code.
+ */
+enum GraphicsTimeline : size_t {
+    /** Time when the app sent the buffer to SurfaceFlinger. */
+    GPU_COMPLETED_TIME = 0,
+
+    /** Time when the frame was presented on the display */
+    PRESENT_TIME = 1,
+
+    /** Total size of the 'GraphicsTimeline' array. Must always be last. */
+    SIZE = 2
+};
+
+/**
  * Generator of unique numbers used to identify input events.
  *
  * Layout of ID:
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index 3e5674e..898d1a9 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -71,6 +71,7 @@
         FOCUS,
         CAPTURE,
         DRAG,
+        TIMELINE,
     };
 
     struct Header {
@@ -195,6 +196,14 @@
 
             inline size_t size() const { return sizeof(Drag); }
         } drag;
+
+        struct Timeline {
+            int32_t eventId;
+            uint32_t empty;
+            std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
+
+            inline size_t size() const { return sizeof(Timeline); }
+        } timeline;
     } __attribute__((aligned(8))) body;
 
     bool isValid(size_t actualSize) const;
@@ -381,10 +390,25 @@
         nsecs_t consumeTime;
     };
 
-    /* Receives the finished signal from the consumer in reply to the original dispatch signal.
-     * If a signal was received, returns a Finished object.
+    struct Timeline {
+        int32_t inputEventId;
+        std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
+    };
+
+    typedef std::variant<Finished, Timeline> ConsumerResponse;
+    /* Receive a signal from the consumer in reply to the original dispatch signal.
+     * If a signal was received, returns a Finished or a Timeline object.
+     * The InputConsumer should return a Finished object for every InputMessage that it is sent
+     * to confirm that it has been processed and that the InputConsumer is responsive.
+     * If several InputMessages are sent to InputConsumer, it's possible to receive Finished
+     * events out of order for those messages.
      *
-     * The returned sequence number is never 0 unless the operation failed.
+     * The Timeline object is returned whenever the receiving end has processed a graphical frame
+     * and is returning the timeline of the frame. Not all input events will cause a Timeline
+     * object to be returned, and there is not guarantee about when it will arrive.
+     *
+     * If an object of Finished is returned, the returned sequence number is never 0 unless the
+     * operation failed.
      *
      * Returned error codes:
      *         OK on success.
@@ -392,7 +416,7 @@
      *         DEAD_OBJECT if the channel's peer has been closed.
      *         Other errors probably indicate that the channel is broken.
      */
-    android::base::Result<Finished> receiveFinishedSignal();
+    android::base::Result<ConsumerResponse> receiveConsumerResponse();
 
 private:
     std::shared_ptr<InputChannel> mChannel;
@@ -448,6 +472,9 @@
      */
     status_t sendFinishedSignal(uint32_t seq, bool handled);
 
+    status_t sendTimeline(int32_t inputEventId,
+                          std::array<nsecs_t, GraphicsTimeline::SIZE> timeline);
+
     /* Returns true if there is a deferred event waiting.
      *
      * Should be called after calling consume() to determine whether the consumer
diff --git a/libs/binder/RpcConnection.cpp b/libs/binder/RpcConnection.cpp
index 83a1618..dab3246 100644
--- a/libs/binder/RpcConnection.cpp
+++ b/libs/binder/RpcConnection.cpp
@@ -20,6 +20,7 @@
 
 #include <binder/Parcel.h>
 #include <binder/Stability.h>
+#include <utils/String8.h>
 
 #include "RpcState.h"
 #include "RpcWireFormat.h"
@@ -29,14 +30,20 @@
 #include <sys/un.h>
 #include <unistd.h>
 
-#if defined(__GLIBC__)
+#ifdef __GLIBC__
 extern "C" pid_t gettid();
 #endif
 
+#ifdef __BIONIC__
+#include <linux/vm_sockets.h>
+#endif
+
 namespace android {
 
 using base::unique_fd;
 
+RpcConnection::SocketAddress::~SocketAddress() {}
+
 RpcConnection::RpcConnection() {
     LOG_RPC_DETAIL("RpcConnection created %p", this);
 
@@ -50,65 +57,68 @@
     return new RpcConnection;
 }
 
+class UnixSocketAddress : public RpcConnection::SocketAddress {
+public:
+    explicit UnixSocketAddress(const char* path) : mAddr({.sun_family = AF_UNIX}) {
+        unsigned int pathLen = strlen(path) + 1;
+        LOG_ALWAYS_FATAL_IF(pathLen > sizeof(mAddr.sun_path), "%u %s", pathLen, path);
+        memcpy(mAddr.sun_path, path, pathLen);
+    }
+    virtual ~UnixSocketAddress() {}
+    std::string toString() const override {
+        return String8::format("path '%.*s'", static_cast<int>(sizeof(mAddr.sun_path)),
+                               mAddr.sun_path)
+                .c_str();
+    }
+    const sockaddr* addr() const override { return reinterpret_cast<const sockaddr*>(&mAddr); }
+    size_t addrSize() const override { return sizeof(mAddr); }
+
+private:
+    sockaddr_un mAddr;
+};
+
 bool RpcConnection::setupUnixDomainServer(const char* path) {
-    LOG_ALWAYS_FATAL_IF(mServer.get() != -1, "Only supports one server now");
-
-    unique_fd serverFd(TEMP_FAILURE_RETRY(socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)));
-    if (serverFd == -1) {
-        ALOGE("Could not create socket at %s: %s", path, strerror(errno));
-        return false;
-    }
-
-    struct sockaddr_un addr = {
-            .sun_family = AF_UNIX,
-    };
-
-    unsigned int pathLen = strlen(path) + 1;
-    LOG_ALWAYS_FATAL_IF(pathLen > sizeof(addr.sun_path), "%u", pathLen);
-    memcpy(addr.sun_path, path, pathLen);
-
-    if (0 != TEMP_FAILURE_RETRY(bind(serverFd.get(), (struct sockaddr*)&addr, sizeof(addr)))) {
-        ALOGE("Could not bind socket at %s: %s", path, strerror(errno));
-        return false;
-    }
-
-    if (0 != TEMP_FAILURE_RETRY(listen(serverFd.get(), 1 /*backlog*/))) {
-        ALOGE("Could not listen socket at %s: %s", path, strerror(errno));
-        return false;
-    }
-
-    mServer = std::move(serverFd);
-    return true;
+    return addServer(UnixSocketAddress(path));
 }
 
 bool RpcConnection::addUnixDomainClient(const char* path) {
-    LOG_RPC_DETAIL("Connecting on path: %s", path);
-
-    unique_fd serverFd(TEMP_FAILURE_RETRY(socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)));
-    if (serverFd == -1) {
-        ALOGE("Could not create socket at %s: %s", path, strerror(errno));
-        return false;
-    }
-
-    struct sockaddr_un addr = {
-            .sun_family = AF_UNIX,
-    };
-
-    unsigned int pathLen = strlen(path) + 1;
-    LOG_ALWAYS_FATAL_IF(pathLen > sizeof(addr.sun_path), "%u", pathLen);
-    memcpy(addr.sun_path, path, pathLen);
-
-    if (0 != TEMP_FAILURE_RETRY(connect(serverFd.get(), (struct sockaddr*)&addr, sizeof(addr)))) {
-        ALOGE("Could not connect socket at %s: %s", path, strerror(errno));
-        return false;
-    }
-
-    LOG_RPC_DETAIL("Unix domain client with fd %d", serverFd.get());
-
-    addClient(std::move(serverFd));
-    return true;
+    return addClient(UnixSocketAddress(path));
 }
 
+#ifdef __BIONIC__
+
+class VsockSocketAddress : public RpcConnection::SocketAddress {
+public:
+    VsockSocketAddress(unsigned int cid, unsigned int port)
+          : mAddr({
+                    .svm_family = AF_VSOCK,
+                    .svm_port = port,
+                    .svm_cid = cid,
+            }) {}
+    virtual ~VsockSocketAddress() {}
+    std::string toString() const override {
+        return String8::format("cid %du port %du", mAddr.svm_cid, mAddr.svm_port).c_str();
+    }
+    const sockaddr* addr() const override { return reinterpret_cast<const sockaddr*>(&mAddr); }
+    size_t addrSize() const override { return sizeof(mAddr); }
+
+private:
+    sockaddr_vm mAddr;
+};
+
+bool RpcConnection::setupVsockServer(unsigned int port) {
+    // realizing value w/ this type at compile time to avoid ubsan abort
+    constexpr unsigned int kAnyCid = VMADDR_CID_ANY;
+
+    return addServer(VsockSocketAddress(kAnyCid, port));
+}
+
+bool RpcConnection::addVsockClient(unsigned int cid, unsigned int port) {
+    return addClient(VsockSocketAddress(cid, port));
+}
+
+#endif // __BIONIC__
+
 sp<IBinder> RpcConnection::getRootObject() {
     ExclusiveSocket socket(this, SocketUse::CLIENT);
     return state()->getRootObject(socket.fd(), this);
@@ -130,11 +140,8 @@
 void RpcConnection::join() {
     // establish a connection
     {
-        struct sockaddr_un clientSa;
-        socklen_t clientSaLen = sizeof(clientSa);
-
-        unique_fd clientFd(TEMP_FAILURE_RETRY(
-                accept4(mServer.get(), (struct sockaddr*)&clientSa, &clientSaLen, SOCK_CLOEXEC)));
+        unique_fd clientFd(
+                TEMP_FAILURE_RETRY(accept4(mServer.get(), nullptr, 0 /*length*/, SOCK_CLOEXEC)));
         if (clientFd < 0) {
             // If this log becomes confusing, should save more state from setupUnixDomainServer
             // in order to output here.
@@ -144,7 +151,7 @@
 
         LOG_RPC_DETAIL("accept4 on fd %d yields fd %d", mServer.get(), clientFd.get());
 
-        addServer(std::move(clientFd));
+        assignServerToThisThread(std::move(clientFd));
     }
 
     // We may not use the connection we just established (two threads might
@@ -170,14 +177,57 @@
     return mForServer;
 }
 
-void RpcConnection::addClient(base::unique_fd&& fd) {
-    std::lock_guard<std::mutex> _l(mSocketMutex);
-    sp<ConnectionSocket> connection = new ConnectionSocket();
-    connection->fd = std::move(fd);
-    mClients.push_back(connection);
+bool RpcConnection::addServer(const SocketAddress& addr) {
+    LOG_ALWAYS_FATAL_IF(mServer.get() != -1, "Each RpcConnection can only have one server.");
+
+    unique_fd serverFd(
+            TEMP_FAILURE_RETRY(socket(addr.addr()->sa_family, SOCK_STREAM | SOCK_CLOEXEC, 0)));
+    if (serverFd == -1) {
+        ALOGE("Could not create socket: %s", strerror(errno));
+        return false;
+    }
+
+    if (0 != TEMP_FAILURE_RETRY(bind(serverFd.get(), addr.addr(), addr.addrSize()))) {
+        int savedErrno = errno;
+        ALOGE("Could not bind socket at %s: %s", addr.toString().c_str(), strerror(savedErrno));
+        return false;
+    }
+
+    if (0 != TEMP_FAILURE_RETRY(listen(serverFd.get(), 1 /*backlog*/))) {
+        int savedErrno = errno;
+        ALOGE("Could not listen socket at %s: %s", addr.toString().c_str(), strerror(savedErrno));
+        return false;
+    }
+
+    mServer = std::move(serverFd);
+    return true;
 }
 
-void RpcConnection::addServer(base::unique_fd&& fd) {
+bool RpcConnection::addClient(const SocketAddress& addr) {
+    unique_fd serverFd(
+            TEMP_FAILURE_RETRY(socket(addr.addr()->sa_family, SOCK_STREAM | SOCK_CLOEXEC, 0)));
+    if (serverFd == -1) {
+        int savedErrno = errno;
+        ALOGE("Could not create socket at %s: %s", addr.toString().c_str(), strerror(savedErrno));
+        return false;
+    }
+
+    if (0 != TEMP_FAILURE_RETRY(connect(serverFd.get(), addr.addr(), addr.addrSize()))) {
+        int savedErrno = errno;
+        ALOGE("Could not connect socket at %s: %s", addr.toString().c_str(), strerror(savedErrno));
+        return false;
+    }
+
+    LOG_RPC_DETAIL("Socket at %s client with fd %d", addr.toString().c_str(), serverFd.get());
+
+    std::lock_guard<std::mutex> _l(mSocketMutex);
+    sp<ConnectionSocket> connection = new ConnectionSocket();
+    connection->fd = std::move(serverFd);
+    mClients.push_back(connection);
+    return true;
+}
+
+void RpcConnection::assignServerToThisThread(base::unique_fd&& fd) {
     std::lock_guard<std::mutex> _l(mSocketMutex);
     sp<ConnectionSocket> connection = new ConnectionSocket();
     connection->fd = std::move(fd);
diff --git a/libs/binder/include/binder/RpcConnection.h b/libs/binder/include/binder/RpcConnection.h
index 65c5232..a300575 100644
--- a/libs/binder/include/binder/RpcConnection.h
+++ b/libs/binder/include/binder/RpcConnection.h
@@ -61,6 +61,18 @@
      */
     [[nodiscard]] bool addUnixDomainClient(const char* path);
 
+#ifdef __BIONIC__
+    /**
+     * Creates an RPC server at the current port.
+     */
+    [[nodiscard]] bool setupVsockServer(unsigned int port);
+
+    /**
+     * Connects to an RPC server at the CVD & port.
+     */
+    [[nodiscard]] bool addVsockClient(unsigned int cvd, unsigned int port);
+#endif // __BIONIC__
+
     /**
      * Query the other side of the connection for the root object hosted by that
      * process's RpcServer (if one exists)
@@ -85,11 +97,20 @@
     // internal only
     const std::unique_ptr<RpcState>& state() { return mState; }
 
+    class SocketAddress {
+    public:
+        virtual ~SocketAddress();
+        virtual std::string toString() const = 0;
+        virtual const sockaddr* addr() const = 0;
+        virtual size_t addrSize() const = 0;
+    };
+
 private:
     RpcConnection();
 
-    void addServer(base::unique_fd&& fd);
-    void addClient(base::unique_fd&& fd);
+    bool addServer(const SocketAddress& address);
+    bool addClient(const SocketAddress& address);
+    void assignServerToThisThread(base::unique_fd&& fd);
 
     struct ConnectionSocket : public RefBase {
         base::unique_fd fd;
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index a44cddf..2a3658e 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -122,6 +122,7 @@
     ],
     test_suites: ["general-tests"],
     require_root: true,
+    host_supported: true,
 }
 
 cc_test {
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index 6fa5333..936ee5e 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -14,14 +14,6 @@
  * limitations under the License.
  */
 
-#include <sys/prctl.h>
-#include <unistd.h>
-
-#include <chrono>
-#include <cstdlib>
-#include <iostream>
-#include <thread>
-
 #include <BnBinderRpcSession.h>
 #include <BnBinderRpcTest.h>
 #include <android-base/logging.h>
@@ -33,6 +25,18 @@
 #include <binder/RpcServer.h>
 #include <gtest/gtest.h>
 
+#include <chrono>
+#include <cstdlib>
+#include <iostream>
+#include <thread>
+
+#ifdef __BIONIC__
+#include <linux/vm_sockets.h>
+#endif //__BIONIC__
+
+#include <sys/prctl.h>
+#include <unistd.h>
+
 #include "../RpcState.h" // for debugging
 
 namespace android {
@@ -185,8 +189,13 @@
 
 static std::string allocateSocketAddress() {
     static size_t id = 0;
+    static bool gUseTmp = access("/tmp/", F_OK) != -1;
 
-    return "/dev/binderRpcTest_" + std::to_string(id++);
+    if (gUseTmp) {
+        return "/tmp/binderRpcTest_" + std::to_string(id++);
+    } else {
+        return "/dev/binderRpcTest_" + std::to_string(id++);
+    }
 };
 
 struct ProcessConnection {
@@ -214,58 +223,6 @@
     }
 };
 
-// This creates a new process serving an interface on a certain number of
-// threads.
-ProcessConnection createRpcTestSocketServerProcess(
-        size_t numThreads,
-        const std::function<void(const sp<RpcServer>&, const sp<RpcConnection>&)>& configure) {
-    CHECK_GT(numThreads, 0);
-
-    std::string addr = allocateSocketAddress();
-    unlink(addr.c_str());
-
-    auto ret = ProcessConnection{
-            .host = Process([&] {
-                sp<RpcServer> server = RpcServer::make();
-
-                server->iUnderstandThisCodeIsExperimentalAndIWillNotUseItInProduction();
-
-                // server supporting one client on one socket
-                sp<RpcConnection> connection = server->addClientConnection();
-                CHECK(connection->setupUnixDomainServer(addr.c_str())) << addr;
-
-                configure(server, connection);
-
-                // accept 'numThreads' connections
-                std::vector<std::thread> pool;
-                for (size_t i = 0; i + 1 < numThreads; i++) {
-                    pool.push_back(std::thread([=] { connection->join(); }));
-                }
-                connection->join();
-                for (auto& t : pool) t.join();
-            }),
-            .connection = RpcConnection::make(),
-    };
-
-    // wait up to 1s for sockets to be created
-    constexpr useconds_t kMaxWaitUs = 1000000;
-    constexpr useconds_t kWaitDivision = 100;
-    for (size_t i = 0; i < kWaitDivision && 0 != access(addr.c_str(), F_OK); i++) {
-        usleep(kMaxWaitUs / kWaitDivision);
-    }
-
-    // create remainder of connections
-    for (size_t i = 0; i < numThreads; i++) {
-        // Connection refused sometimes after file created but before listening.
-        CHECK(ret.connection->addUnixDomainClient(addr.c_str()) ||
-              (usleep(10000), ret.connection->addUnixDomainClient(addr.c_str())))
-                << i;
-    }
-
-    ret.rootBinder = ret.connection->getRootObject();
-    return ret;
-}
-
 // Process connection where the process hosts IBinderRpcTest, the server used
 // for most testing here
 struct BinderRpcTestProcessConnection {
@@ -290,26 +247,122 @@
     }
 };
 
-BinderRpcTestProcessConnection createRpcTestSocketServerProcess(size_t numThreads) {
-    BinderRpcTestProcessConnection ret{
-            .proc = createRpcTestSocketServerProcess(numThreads,
-                                                     [&](const sp<RpcServer>& server,
-                                                         const sp<RpcConnection>& connection) {
-                                                         sp<MyBinderRpcTest> service =
-                                                                 new MyBinderRpcTest;
-                                                         server->setRootObject(service);
-                                                         service->connection =
-                                                                 connection; // for testing only
-                                                     }),
-    };
-
-    ret.rootBinder = ret.proc.rootBinder;
-    ret.rootIface = interface_cast<IBinderRpcTest>(ret.rootBinder);
-
-    return ret;
+enum class SocketType {
+    UNIX,
+#ifdef __BIONIC__
+    VSOCK,
+#endif // __BIONIC__
+};
+static inline std::string PrintSocketType(const testing::TestParamInfo<SocketType>& info) {
+    switch (info.param) {
+        case SocketType::UNIX:
+            return "unix_domain_socket";
+#ifdef __BIONIC__
+        case SocketType::VSOCK:
+            return "vm_socket";
+#endif // __BIONIC__
+        default:
+            LOG_ALWAYS_FATAL("Unknown socket type");
+            return "";
+    }
 }
+class BinderRpc : public ::testing::TestWithParam<SocketType> {
+public:
+    // This creates a new process serving an interface on a certain number of
+    // threads.
+    ProcessConnection createRpcTestSocketServerProcess(
+            size_t numThreads,
+            const std::function<void(const sp<RpcServer>&, const sp<RpcConnection>&)>& configure) {
+        CHECK_GT(numThreads, 0);
 
-TEST(BinderRpc, RootObjectIsNull) {
+        SocketType socketType = GetParam();
+
+        std::string addr = allocateSocketAddress();
+        unlink(addr.c_str());
+        static unsigned int port = 3456;
+        port++;
+
+        auto ret = ProcessConnection{
+                .host = Process([&] {
+                    sp<RpcServer> server = RpcServer::make();
+
+                    server->iUnderstandThisCodeIsExperimentalAndIWillNotUseItInProduction();
+
+                    // server supporting one client on one socket
+                    sp<RpcConnection> connection = server->addClientConnection();
+
+                    switch (socketType) {
+                        case SocketType::UNIX:
+                            CHECK(connection->setupUnixDomainServer(addr.c_str())) << addr;
+                            break;
+#ifdef __BIONIC__
+                        case SocketType::VSOCK:
+                            CHECK(connection->setupVsockServer(port));
+                            break;
+#endif // __BIONIC__
+                        default:
+                            LOG_ALWAYS_FATAL("Unknown socket type");
+                    }
+
+                    configure(server, connection);
+
+                    // accept 'numThreads' connections
+                    std::vector<std::thread> pool;
+                    for (size_t i = 0; i + 1 < numThreads; i++) {
+                        pool.push_back(std::thread([=] { connection->join(); }));
+                    }
+                    connection->join();
+                    for (auto& t : pool) t.join();
+                }),
+                .connection = RpcConnection::make(),
+        };
+
+        // create remainder of connections
+        for (size_t i = 0; i < numThreads; i++) {
+            for (size_t tries = 0; tries < 5; tries++) {
+                usleep(10000);
+                switch (socketType) {
+                    case SocketType::UNIX:
+                        if (ret.connection->addUnixDomainClient(addr.c_str())) goto success;
+                        break;
+#ifdef __BIONIC__
+                    case SocketType::VSOCK:
+                        if (ret.connection->addVsockClient(VMADDR_CID_LOCAL, port)) goto success;
+                        break;
+#endif // __BIONIC__
+                    default:
+                        LOG_ALWAYS_FATAL("Unknown socket type");
+                }
+            }
+            LOG_ALWAYS_FATAL("Could not connect");
+        success:;
+        }
+
+        ret.rootBinder = ret.connection->getRootObject();
+        return ret;
+    }
+
+    BinderRpcTestProcessConnection createRpcTestSocketServerProcess(size_t numThreads) {
+        BinderRpcTestProcessConnection ret{
+                .proc = createRpcTestSocketServerProcess(numThreads,
+                                                         [&](const sp<RpcServer>& server,
+                                                             const sp<RpcConnection>& connection) {
+                                                             sp<MyBinderRpcTest> service =
+                                                                     new MyBinderRpcTest;
+                                                             server->setRootObject(service);
+                                                             service->connection =
+                                                                     connection; // for testing only
+                                                         }),
+        };
+
+        ret.rootBinder = ret.proc.rootBinder;
+        ret.rootIface = interface_cast<IBinderRpcTest>(ret.rootBinder);
+
+        return ret;
+    }
+};
+
+TEST_P(BinderRpc, RootObjectIsNull) {
     auto proc = createRpcTestSocketServerProcess(1,
                                                  [](const sp<RpcServer>& server,
                                                     const sp<RpcConnection>&) {
@@ -324,20 +377,20 @@
     EXPECT_EQ(nullptr, proc.connection->getRootObject());
 }
 
-TEST(BinderRpc, Ping) {
+TEST_P(BinderRpc, Ping) {
     auto proc = createRpcTestSocketServerProcess(1);
     ASSERT_NE(proc.rootBinder, nullptr);
     EXPECT_EQ(OK, proc.rootBinder->pingBinder());
 }
 
-TEST(BinderRpc, TransactionsMustBeMarkedRpc) {
+TEST_P(BinderRpc, TransactionsMustBeMarkedRpc) {
     auto proc = createRpcTestSocketServerProcess(1);
     Parcel data;
     Parcel reply;
     EXPECT_EQ(BAD_TYPE, proc.rootBinder->transact(IBinder::PING_TRANSACTION, data, &reply, 0));
 }
 
-TEST(BinderRpc, UnknownTransaction) {
+TEST_P(BinderRpc, UnknownTransaction) {
     auto proc = createRpcTestSocketServerProcess(1);
     Parcel data;
     data.markForBinder(proc.rootBinder);
@@ -345,19 +398,19 @@
     EXPECT_EQ(UNKNOWN_TRANSACTION, proc.rootBinder->transact(1337, data, &reply, 0));
 }
 
-TEST(BinderRpc, SendSomethingOneway) {
+TEST_P(BinderRpc, SendSomethingOneway) {
     auto proc = createRpcTestSocketServerProcess(1);
     EXPECT_OK(proc.rootIface->sendString("asdf"));
 }
 
-TEST(BinderRpc, SendAndGetResultBack) {
+TEST_P(BinderRpc, SendAndGetResultBack) {
     auto proc = createRpcTestSocketServerProcess(1);
     std::string doubled;
     EXPECT_OK(proc.rootIface->doubleString("cool ", &doubled));
     EXPECT_EQ("cool cool ", doubled);
 }
 
-TEST(BinderRpc, SendAndGetResultBackBig) {
+TEST_P(BinderRpc, SendAndGetResultBackBig) {
     auto proc = createRpcTestSocketServerProcess(1);
     std::string single = std::string(1024, 'a');
     std::string doubled;
@@ -365,7 +418,7 @@
     EXPECT_EQ(single + single, doubled);
 }
 
-TEST(BinderRpc, CallMeBack) {
+TEST_P(BinderRpc, CallMeBack) {
     auto proc = createRpcTestSocketServerProcess(1);
 
     int32_t pingResult;
@@ -375,7 +428,7 @@
     EXPECT_EQ(0, MyBinderRpcSession::gNum);
 }
 
-TEST(BinderRpc, RepeatBinder) {
+TEST_P(BinderRpc, RepeatBinder) {
     auto proc = createRpcTestSocketServerProcess(1);
 
     sp<IBinder> inBinder = new MyBinderRpcSession("foo");
@@ -397,7 +450,7 @@
     EXPECT_EQ(0, MyBinderRpcSession::gNum);
 }
 
-TEST(BinderRpc, RepeatTheirBinder) {
+TEST_P(BinderRpc, RepeatTheirBinder) {
     auto proc = createRpcTestSocketServerProcess(1);
 
     sp<IBinderRpcSession> session;
@@ -421,7 +474,7 @@
     EXPECT_EQ(nullptr, weak.promote());
 }
 
-TEST(BinderRpc, RepeatBinderNull) {
+TEST_P(BinderRpc, RepeatBinderNull) {
     auto proc = createRpcTestSocketServerProcess(1);
 
     sp<IBinder> outBinder;
@@ -429,7 +482,7 @@
     EXPECT_EQ(nullptr, outBinder);
 }
 
-TEST(BinderRpc, HoldBinder) {
+TEST_P(BinderRpc, HoldBinder) {
     auto proc = createRpcTestSocketServerProcess(1);
 
     IBinder* ptr = nullptr;
@@ -455,7 +508,7 @@
 // These are behavioral differences form regular binder, where certain usecases
 // aren't supported.
 
-TEST(BinderRpc, CannotMixBindersBetweenUnrelatedSocketConnections) {
+TEST_P(BinderRpc, CannotMixBindersBetweenUnrelatedSocketConnections) {
     auto proc1 = createRpcTestSocketServerProcess(1);
     auto proc2 = createRpcTestSocketServerProcess(1);
 
@@ -464,7 +517,7 @@
               proc1.rootIface->repeatBinder(proc2.rootBinder, &outBinder).transactionError());
 }
 
-TEST(BinderRpc, CannotSendRegularBinderOverSocketBinder) {
+TEST_P(BinderRpc, CannotSendRegularBinderOverSocketBinder) {
     auto proc = createRpcTestSocketServerProcess(1);
 
     sp<IBinder> someRealBinder = IInterface::asBinder(defaultServiceManager());
@@ -473,7 +526,7 @@
               proc.rootIface->repeatBinder(someRealBinder, &outBinder).transactionError());
 }
 
-TEST(BinderRpc, CannotSendSocketBinderOverRegularBinder) {
+TEST_P(BinderRpc, CannotSendSocketBinderOverRegularBinder) {
     auto proc = createRpcTestSocketServerProcess(1);
 
     // for historical reasons, IServiceManager interface only returns the
@@ -484,7 +537,7 @@
 
 // END TESTS FOR LIMITATIONS OF SOCKET BINDER
 
-TEST(BinderRpc, RepeatRootObject) {
+TEST_P(BinderRpc, RepeatRootObject) {
     auto proc = createRpcTestSocketServerProcess(1);
 
     sp<IBinder> outBinder;
@@ -492,7 +545,7 @@
     EXPECT_EQ(proc.rootBinder, outBinder);
 }
 
-TEST(BinderRpc, NestedTransactions) {
+TEST_P(BinderRpc, NestedTransactions) {
     auto proc = createRpcTestSocketServerProcess(1);
 
     auto nastyNester = sp<MyBinderRpcTest>::make();
@@ -503,7 +556,7 @@
     EXPECT_EQ(nullptr, weak.promote());
 }
 
-TEST(BinderRpc, SameBinderEquality) {
+TEST_P(BinderRpc, SameBinderEquality) {
     auto proc = createRpcTestSocketServerProcess(1);
 
     sp<IBinder> a;
@@ -515,7 +568,7 @@
     EXPECT_EQ(a, b);
 }
 
-TEST(BinderRpc, SameBinderEqualityWeak) {
+TEST_P(BinderRpc, SameBinderEqualityWeak) {
     auto proc = createRpcTestSocketServerProcess(1);
 
     sp<IBinder> a;
@@ -547,7 +600,7 @@
         EXPECT_EQ(expected, session);                     \
     } while (false)
 
-TEST(BinderRpc, SingleSession) {
+TEST_P(BinderRpc, SingleSession) {
     auto proc = createRpcTestSocketServerProcess(1);
 
     sp<IBinderRpcSession> session;
@@ -561,7 +614,7 @@
     expectSessions(0, proc.rootIface);
 }
 
-TEST(BinderRpc, ManySessions) {
+TEST_P(BinderRpc, ManySessions) {
     auto proc = createRpcTestSocketServerProcess(1);
 
     std::vector<sp<IBinderRpcSession>> sessions;
@@ -595,7 +648,7 @@
     return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
 }
 
-TEST(BinderRpc, ThreadPoolGreaterThanEqualRequested) {
+TEST_P(BinderRpc, ThreadPoolGreaterThanEqualRequested) {
     constexpr size_t kNumThreads = 10;
 
     auto proc = createRpcTestSocketServerProcess(kNumThreads);
@@ -627,7 +680,7 @@
     for (auto& t : ts) t.join();
 }
 
-TEST(BinderRpc, ThreadPoolOverSaturated) {
+TEST_P(BinderRpc, ThreadPoolOverSaturated) {
     constexpr size_t kNumThreads = 10;
     constexpr size_t kNumCalls = kNumThreads + 3;
     constexpr size_t kSleepMs = 500;
@@ -651,7 +704,7 @@
     EXPECT_LE(epochMsAfter, epochMsBefore + 3 * kSleepMs);
 }
 
-TEST(BinderRpc, ThreadingStressTest) {
+TEST_P(BinderRpc, ThreadingStressTest) {
     constexpr size_t kNumClientThreads = 10;
     constexpr size_t kNumServerThreads = 10;
     constexpr size_t kNumCalls = 100;
@@ -672,7 +725,7 @@
     for (auto& t : threads) t.join();
 }
 
-TEST(BinderRpc, OnewayCallDoesNotWait) {
+TEST_P(BinderRpc, OnewayCallDoesNotWait) {
     constexpr size_t kReallyLongTimeMs = 100;
     constexpr size_t kSleepMs = kReallyLongTimeMs * 5;
 
@@ -687,7 +740,7 @@
     EXPECT_LT(epochMsAfter, epochMsBefore + kReallyLongTimeMs);
 }
 
-TEST(BinderRpc, OnewayCallQueueing) {
+TEST_P(BinderRpc, OnewayCallQueueing) {
     constexpr size_t kNumSleeps = 10;
     constexpr size_t kNumExtraServerThreads = 4;
     constexpr size_t kSleepMs = 50;
@@ -711,7 +764,7 @@
     EXPECT_GT(epochMsAfter, epochMsBefore + kSleepMs * kNumSleeps);
 }
 
-TEST(BinderRpc, Die) {
+TEST_P(BinderRpc, Die) {
     // TODO(b/183141167): handle this in library
     signal(SIGPIPE, SIG_IGN);
 
@@ -743,7 +796,7 @@
     return ret;
 }
 
-TEST(BinderRpc, Fds) {
+TEST_P(BinderRpc, Fds) {
     ssize_t beforeFds = countFds();
     ASSERT_GE(beforeFds, 0);
     {
@@ -753,10 +806,19 @@
     ASSERT_EQ(beforeFds, countFds()) << (system("ls -l /proc/self/fd/"), "fd leak?");
 }
 
-extern "C" int main(int argc, char** argv) {
+INSTANTIATE_TEST_CASE_P(PerSocket, BinderRpc,
+                        ::testing::Values(SocketType::UNIX
+#ifdef __BIONIC__
+                                          ,
+                                          SocketType::VSOCK
+#endif // __BIONIC__
+                                          ),
+                        PrintSocketType);
+
+} // namespace android
+
+int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
     android::base::InitLogging(argv, android::base::StderrLogger, android::base::DefaultAborter);
     return RUN_ALL_TESTS();
 }
-
-} // namespace android
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 1976b49..bcdd06b 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -555,11 +555,13 @@
         }).detach();
     }
 
-    status_t setFrameRate(float frameRate, int8_t compatibility, bool shouldBeSeamless) override {
-        if (!ValidateFrameRate(frameRate, compatibility, "BBQSurface::setFrameRate")) {
+    status_t setFrameRate(float frameRate, int8_t compatibility,
+                          int8_t changeFrameRateStrategy) override {
+        if (!ValidateFrameRate(frameRate, compatibility, changeFrameRateStrategy,
+                               "BBQSurface::setFrameRate")) {
             return BAD_VALUE;
         }
-        return mBbq->setFrameRate(frameRate, compatibility, shouldBeSeamless);
+        return mBbq->setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
     }
 
     status_t setFrameTimelineInfo(const FrameTimelineInfo& frameTimelineInfo) override {
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index 97f8f47..c1e3385 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -1033,39 +1033,15 @@
     }
 
     status_t setFrameRate(const sp<IGraphicBufferProducer>& surface, float frameRate,
-                          int8_t compatibility, bool shouldBeSeamless) override {
+                          int8_t compatibility, int8_t changeFrameRateStrategy) override {
         Parcel data, reply;
-        status_t err = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        if (err != NO_ERROR) {
-            ALOGE("setFrameRate: failed writing interface token: %s (%d)", strerror(-err), -err);
-            return err;
-        }
+        SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor());
+        SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(surface));
+        SAFE_PARCEL(data.writeFloat, frameRate);
+        SAFE_PARCEL(data.writeByte, compatibility);
+        SAFE_PARCEL(data.writeByte, changeFrameRateStrategy);
 
-        err = data.writeStrongBinder(IInterface::asBinder(surface));
-        if (err != NO_ERROR) {
-            ALOGE("setFrameRate: failed writing strong binder: %s (%d)", strerror(-err), -err);
-            return err;
-        }
-
-        err = data.writeFloat(frameRate);
-        if (err != NO_ERROR) {
-            ALOGE("setFrameRate: failed writing float: %s (%d)", strerror(-err), -err);
-            return err;
-        }
-
-        err = data.writeByte(compatibility);
-        if (err != NO_ERROR) {
-            ALOGE("setFrameRate: failed writing byte: %s (%d)", strerror(-err), -err);
-            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);
+        status_t err = remote()->transact(BnSurfaceComposer::SET_FRAME_RATE, data, &reply);
         if (err != NO_ERROR) {
             ALOGE("setFrameRate: failed to transact: %s (%d)", strerror(-err), err);
             return err;
@@ -1894,36 +1870,24 @@
         case SET_FRAME_RATE: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             sp<IBinder> binder;
-            status_t err = data.readStrongBinder(&binder);
-            if (err != NO_ERROR) {
-                ALOGE("setFrameRate: failed to read strong binder: %s (%d)", strerror(-err), -err);
-                return err;
-            }
+            SAFE_PARCEL(data.readStrongBinder, &binder);
+
             sp<IGraphicBufferProducer> surface = interface_cast<IGraphicBufferProducer>(binder);
             if (!surface) {
-                ALOGE("setFrameRate: failed to cast to IGraphicBufferProducer: %s (%d)",
-                      strerror(-err), -err);
-                return err;
+                ALOGE("setFrameRate: failed to cast to IGraphicBufferProducer");
+                return BAD_VALUE;
             }
             float frameRate;
-            err = data.readFloat(&frameRate);
-            if (err != NO_ERROR) {
-                ALOGE("setFrameRate: failed to read float: %s (%d)", strerror(-err), -err);
-                return err;
-            }
+            SAFE_PARCEL(data.readFloat, &frameRate);
+
             int8_t compatibility;
-            err = data.readByte(&compatibility);
-            if (err != NO_ERROR) {
-                ALOGE("setFrameRate: failed to read byte: %s (%d)", strerror(-err), -err);
-                return err;
-            }
-            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);
+            SAFE_PARCEL(data.readByte, &compatibility);
+
+            int8_t changeFrameRateStrategy;
+            SAFE_PARCEL(data.readByte, &changeFrameRateStrategy);
+
+            status_t result =
+                    setFrameRate(surface, frameRate, compatibility, changeFrameRateStrategy);
             reply->writeInt32(result);
             return NO_ERROR;
         }
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 467dcda..2a9a97e 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -19,6 +19,7 @@
 #include <apex/window.h>
 #include <inttypes.h>
 
+#include <android/native_window.h>
 #include <binder/Parcel.h>
 #include <gui/IGraphicBufferProducer.h>
 #include <gui/ISurfaceComposerClient.h>
@@ -60,7 +61,7 @@
         frameRateSelectionPriority(-1),
         frameRate(0.0f),
         frameRateCompatibility(ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT),
-        shouldBeSeamless(true),
+        changeFrameRateStrategy(ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS),
         fixedTransformHint(ui::Transform::ROT_INVALID),
         frameNumber(0),
         autoRefresh(false),
@@ -148,7 +149,7 @@
     SAFE_PARCEL(output.writeInt32, frameRateSelectionPriority);
     SAFE_PARCEL(output.writeFloat, frameRate);
     SAFE_PARCEL(output.writeByte, frameRateCompatibility);
-    SAFE_PARCEL(output.writeBool, shouldBeSeamless);
+    SAFE_PARCEL(output.writeByte, changeFrameRateStrategy);
     SAFE_PARCEL(output.writeUint32, fixedTransformHint);
     SAFE_PARCEL(output.writeUint64, frameNumber);
     SAFE_PARCEL(output.writeBool, autoRefresh);
@@ -269,7 +270,7 @@
     SAFE_PARCEL(input.readInt32, &frameRateSelectionPriority);
     SAFE_PARCEL(input.readFloat, &frameRate);
     SAFE_PARCEL(input.readByte, &frameRateCompatibility);
-    SAFE_PARCEL(input.readBool, &shouldBeSeamless);
+    SAFE_PARCEL(input.readByte, &changeFrameRateStrategy);
     SAFE_PARCEL(input.readUint32, &tmpUint32);
     fixedTransformHint = static_cast<ui::Transform::RotationFlags>(tmpUint32);
     SAFE_PARCEL(input.readUint64, &frameNumber);
@@ -531,7 +532,7 @@
         what |= eFrameRateChanged;
         frameRate = other.frameRate;
         frameRateCompatibility = other.frameRateCompatibility;
-        shouldBeSeamless = other.shouldBeSeamless;
+        changeFrameRateStrategy = other.changeFrameRateStrategy;
     }
     if (other.what & eFixedTransformHintChanged) {
         what |= eFixedTransformHintChanged;
@@ -628,8 +629,8 @@
     return NO_ERROR;
 }
 
-bool ValidateFrameRate(float frameRate, int8_t compatibility, const char* inFunctionName,
-                       bool privileged) {
+bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy,
+                       const char* inFunctionName, bool privileged) {
     const char* functionName = inFunctionName != nullptr ? inFunctionName : "call";
     int floatClassification = std::fpclassify(frameRate);
     if (frameRate < 0 || floatClassification == FP_INFINITE || floatClassification == FP_NAN) {
@@ -645,6 +646,12 @@
         return false;
     }
 
+    if (changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS &&
+        changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS) {
+        ALOGE("%s failed - invalid change frame rate strategy value %d", functionName,
+              changeFrameRateStrategy);
+    }
+
     return true;
 }
 
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 6de3e97..2fc9d47 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -1729,8 +1729,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));
-    bool shouldBeSeamless = static_cast<bool>(va_arg(args, int));
-    return setFrameRate(frameRate, compatibility, shouldBeSeamless);
+    int8_t changeFrameRateStrategy = static_cast<int8_t>(va_arg(args, int));
+    return setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
 }
 
 int Surface::dispatchAddCancelInterceptor(va_list args) {
@@ -2575,16 +2575,18 @@
     mSurfaceListener->onBuffersDiscarded(discardedBufs);
 }
 
-status_t Surface::setFrameRate(float frameRate, int8_t compatibility, bool shouldBeSeamless) {
+status_t Surface::setFrameRate(float frameRate, int8_t compatibility,
+                               int8_t changeFrameRateStrategy) {
     ATRACE_CALL();
     ALOGV("Surface::setFrameRate");
 
-    if (!ValidateFrameRate(frameRate, compatibility, "Surface::setFrameRate")) {
+    if (!ValidateFrameRate(frameRate, compatibility, changeFrameRateStrategy,
+                           "Surface::setFrameRate")) {
         return BAD_VALUE;
     }
 
     return composerService()->setFrameRate(mGraphicBufferProducer, frameRate, compatibility,
-                                           shouldBeSeamless);
+                                           changeFrameRateStrategy);
 }
 
 status_t Surface::setFrameTimelineInfo(const FrameTimelineInfo& frameTimelineInfo) {
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 0b01084..cb8028b 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -1583,7 +1583,7 @@
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFrameRate(
         const sp<SurfaceControl>& sc, float frameRate, int8_t compatibility,
-        bool shouldBeSeamless) {
+        int8_t changeFrameRateStrategy) {
     layer_state_t* s = getLayerState(sc);
     if (!s) {
         mStatus = BAD_INDEX;
@@ -1591,7 +1591,8 @@
     }
     // Allow privileged values as well here, those will be ignored by SF if
     // the caller is not privileged
-    if (!ValidateFrameRate(frameRate, compatibility, "Transaction::setFrameRate",
+    if (!ValidateFrameRate(frameRate, compatibility, changeFrameRateStrategy,
+                           "Transaction::setFrameRate",
                            /*privileged=*/true)) {
         mStatus = BAD_VALUE;
         return *this;
@@ -1599,7 +1600,7 @@
     s->what |= layer_state_t::eFrameRateChanged;
     s->frameRate = frameRate;
     s->frameRateCompatibility = compatibility;
-    s->shouldBeSeamless = shouldBeSeamless;
+    s->changeFrameRateStrategy = changeFrameRateStrategy;
     return *this;
 }
 
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 9f9ca74..d933514 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -466,7 +466,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, bool shouldBeSeamless) = 0;
+                                  int8_t compatibility, int8_t changeFrameRateStrategy) = 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 11f0961..6c265c8 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -208,7 +208,7 @@
     // Layer frame rate and compatibility. See ANativeWindow_setFrameRate().
     float frameRate;
     int8_t frameRateCompatibility;
-    bool shouldBeSeamless;
+    int8_t changeFrameRateStrategy;
 
     // Set by window manager indicating the layer and all its children are
     // in a different orientation than the display. The hint suggests that
@@ -308,10 +308,11 @@
 //
 // @param frameRate the frame rate in Hz
 // @param compatibility a ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_*
+// @param changeFrameRateStrategy a ANATIVEWINDOW_CHANGE_FRAME_RATE_*
 // @param functionName calling function or nullptr. Used for logging
 // @param privileged whether caller has unscoped surfaceflinger access
-bool ValidateFrameRate(float frameRate, int8_t compatibility, const char* functionName,
-                       bool privileged = false);
+bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy,
+                       const char* functionName, bool privileged = false);
 
 struct CaptureArgs {
     const static int32_t UNSET_UID = -1;
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index 5881221..d22bdaa 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -187,7 +187,8 @@
     status_t getUniqueId(uint64_t* outId) const;
     status_t getConsumerUsage(uint64_t* outUsage) const;
 
-    virtual status_t setFrameRate(float frameRate, int8_t compatibility, bool shouldBeSeamless);
+    virtual status_t setFrameRate(float frameRate, int8_t compatibility,
+                                  int8_t changeFrameRateStrategy);
     virtual status_t setFrameTimelineInfo(const FrameTimelineInfo& info);
     virtual status_t getExtraBufferCount(int* extraBuffers) const;
 
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index c38375c..35f57a2 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -515,7 +515,7 @@
         Transaction& setShadowRadius(const sp<SurfaceControl>& sc, float cornerRadius);
 
         Transaction& setFrameRate(const sp<SurfaceControl>& sc, float frameRate,
-                                  int8_t compatibility, bool shouldBeSeamless);
+                                  int8_t compatibility, int8_t changeFrameRateStrategy);
 
         // 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 5ac3f19..8876033 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -859,7 +859,7 @@
     }
 
     status_t setFrameRate(const sp<IGraphicBufferProducer>& /*surface*/, float /*frameRate*/,
-                          int8_t /*compatibility*/, bool /*shouldBeSeamless*/) override {
+                          int8_t /*compatibility*/, int8_t /*changeFrameRateStrategy*/) override {
         return NO_ERROR;
     }
 
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index c2a3cf1..f7962e0 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -110,6 +110,12 @@
                 return true;
             case Type::DRAG:
                 return true;
+            case Type::TIMELINE:
+                const nsecs_t gpuCompletedTime =
+                        body.timeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME];
+                const nsecs_t presentTime =
+                        body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME];
+                return presentTime > gpuCompletedTime;
         }
     }
     return false;
@@ -129,6 +135,8 @@
             return sizeof(Header) + body.capture.size();
         case Type::DRAG:
             return sizeof(Header) + body.drag.size();
+        case Type::TIMELINE:
+            return sizeof(Header) + body.timeline.size();
     }
     return sizeof(Header);
 }
@@ -260,6 +268,11 @@
             msg->body.drag.isExiting = body.drag.isExiting;
             break;
         }
+        case InputMessage::Type::TIMELINE: {
+            msg->body.timeline.eventId = body.timeline.eventId;
+            msg->body.timeline.graphicsTimeline = body.timeline.graphicsTimeline;
+            break;
+        }
     }
 }
 
@@ -629,7 +642,7 @@
     return mChannel->sendMessage(&msg);
 }
 
-android::base::Result<InputPublisher::Finished> InputPublisher::receiveFinishedSignal() {
+android::base::Result<InputPublisher::ConsumerResponse> InputPublisher::receiveConsumerResponse() {
     if (DEBUG_TRANSPORT_ACTIONS) {
         ALOGD("channel '%s' publisher ~ %s", mChannel->getName().c_str(), __func__);
     }
@@ -639,16 +652,24 @@
     if (result) {
         return android::base::Error(result);
     }
-    if (msg.header.type != InputMessage::Type::FINISHED) {
-        ALOGE("channel '%s' publisher ~ Received unexpected %s message from consumer",
-              mChannel->getName().c_str(), NamedEnum::string(msg.header.type).c_str());
-        return android::base::Error(UNKNOWN_ERROR);
+    if (msg.header.type == InputMessage::Type::FINISHED) {
+        return Finished{
+                .seq = msg.header.seq,
+                .handled = msg.body.finished.handled,
+                .consumeTime = msg.body.finished.consumeTime,
+        };
     }
-    return Finished{
-            .seq = msg.header.seq,
-            .handled = msg.body.finished.handled,
-            .consumeTime = msg.body.finished.consumeTime,
-    };
+
+    if (msg.header.type == InputMessage::Type::TIMELINE) {
+        return Timeline{
+                .inputEventId = msg.body.timeline.eventId,
+                .graphicsTimeline = msg.body.timeline.graphicsTimeline,
+        };
+    }
+
+    ALOGE("channel '%s' publisher ~ Received unexpected %s message from consumer",
+          mChannel->getName().c_str(), NamedEnum::string(msg.header.type).c_str());
+    return android::base::Error(UNKNOWN_ERROR);
 }
 
 // --- InputConsumer ---
@@ -785,7 +806,8 @@
                 break;
             }
 
-            case InputMessage::Type::FINISHED: {
+            case InputMessage::Type::FINISHED:
+            case InputMessage::Type::TIMELINE: {
                 LOG_ALWAYS_FATAL("Consumed a %s message, which should never be seen by "
                                  "InputConsumer!",
                                  NamedEnum::string(mMsg.header.type).c_str());
@@ -1193,6 +1215,24 @@
     return sendUnchainedFinishedSignal(seq, handled);
 }
 
+status_t InputConsumer::sendTimeline(int32_t inputEventId,
+                                     std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline) {
+    if (DEBUG_TRANSPORT_ACTIONS) {
+        ALOGD("channel '%s' consumer ~ sendTimeline: inputEventId=%" PRId32
+              ", gpuCompletedTime=%" PRId64 ", presentTime=%" PRId64,
+              mChannel->getName().c_str(), inputEventId,
+              graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME],
+              graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
+    }
+
+    InputMessage msg;
+    msg.header.type = InputMessage::Type::TIMELINE;
+    msg.header.seq = 0;
+    msg.body.timeline.eventId = inputEventId;
+    msg.body.timeline.graphicsTimeline = std::move(graphicsTimeline);
+    return mChannel->sendMessage(&msg);
+}
+
 nsecs_t InputConsumer::getConsumeTime(uint32_t seq) const {
     auto it = mConsumeTimes.find(seq);
     // Consume time will be missing if either 'finishInputEvent' is called twice, or if it was
@@ -1399,6 +1439,19 @@
                                                        toString(msg.body.drag.isExiting));
                     break;
                 }
+                case InputMessage::Type::TIMELINE: {
+                    const nsecs_t gpuCompletedTime =
+                            msg.body.timeline
+                                    .graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME];
+                    const nsecs_t presentTime =
+                            msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME];
+                    out += android::base::StringPrintf("inputEventId=%" PRId32
+                                                       ", gpuCompletedTime=%" PRId64
+                                                       ", presentTime=%" PRId64,
+                                                       msg.body.timeline.eventId, gpuCompletedTime,
+                                                       presentTime);
+                    break;
+                }
             }
             out += "\n";
         }
diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp
index fc31715..088e00b 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -124,13 +124,15 @@
     ASSERT_EQ(OK, status)
             << "consumer sendFinishedSignal should return OK";
 
-    Result<InputPublisher::Finished> result = mPublisher->receiveFinishedSignal();
-    ASSERT_TRUE(result.ok()) << "publisher receiveFinishedSignal should return OK";
-    ASSERT_EQ(seq, result->seq)
-            << "receiveFinishedSignal should have returned the original sequence number";
-    ASSERT_TRUE(result->handled)
-            << "receiveFinishedSignal should have set handled to consumer's reply";
-    ASSERT_GE(result->consumeTime, publishTime)
+    Result<InputPublisher::ConsumerResponse> result = mPublisher->receiveConsumerResponse();
+    ASSERT_TRUE(result.ok()) << "receiveConsumerResponse should return OK";
+    ASSERT_TRUE(std::holds_alternative<InputPublisher::Finished>(*result));
+    const InputPublisher::Finished& finish = std::get<InputPublisher::Finished>(*result);
+    ASSERT_EQ(seq, finish.seq)
+            << "receiveConsumerResponse should have returned the original sequence number";
+    ASSERT_TRUE(finish.handled)
+            << "receiveConsumerResponse should have set handled to consumer's reply";
+    ASSERT_GE(finish.consumeTime, publishTime)
             << "finished signal's consume time should be greater than publish time";
 }
 
@@ -264,13 +266,15 @@
     ASSERT_EQ(OK, status)
             << "consumer sendFinishedSignal should return OK";
 
-    Result<InputPublisher::Finished> result = mPublisher->receiveFinishedSignal();
-    ASSERT_TRUE(result.ok()) << "receiveFinishedSignal should return OK";
-    ASSERT_EQ(seq, result->seq)
-            << "receiveFinishedSignal should have returned the original sequence number";
-    ASSERT_FALSE(result->handled)
-            << "receiveFinishedSignal should have set handled to consumer's reply";
-    ASSERT_GE(result->consumeTime, publishTime)
+    Result<InputPublisher::ConsumerResponse> result = mPublisher->receiveConsumerResponse();
+    ASSERT_TRUE(result.ok()) << "receiveConsumerResponse should return OK";
+    ASSERT_TRUE(std::holds_alternative<InputPublisher::Finished>(*result));
+    const InputPublisher::Finished& finish = std::get<InputPublisher::Finished>(*result);
+    ASSERT_EQ(seq, finish.seq)
+            << "receiveConsumerResponse should have returned the original sequence number";
+    ASSERT_FALSE(finish.handled)
+            << "receiveConsumerResponse should have set handled to consumer's reply";
+    ASSERT_GE(finish.consumeTime, publishTime)
             << "finished signal's consume time should be greater than publish time";
 }
 
@@ -304,14 +308,16 @@
     status = mConsumer->sendFinishedSignal(seq, true);
     ASSERT_EQ(OK, status) << "consumer sendFinishedSignal should return OK";
 
-    Result<InputPublisher::Finished> result = mPublisher->receiveFinishedSignal();
+    Result<InputPublisher::ConsumerResponse> result = mPublisher->receiveConsumerResponse();
+    ASSERT_TRUE(result.ok()) << "receiveConsumerResponse should return OK";
+    ASSERT_TRUE(std::holds_alternative<InputPublisher::Finished>(*result));
+    const InputPublisher::Finished& finish = std::get<InputPublisher::Finished>(*result);
 
-    ASSERT_TRUE(result.ok()) << "receiveFinishedSignal should return OK";
-    ASSERT_EQ(seq, result->seq)
-            << "receiveFinishedSignal should have returned the original sequence number";
-    ASSERT_TRUE(result->handled)
-            << "receiveFinishedSignal should have set handled to consumer's reply";
-    ASSERT_GE(result->consumeTime, publishTime)
+    ASSERT_EQ(seq, finish.seq)
+            << "receiveConsumerResponse should have returned the original sequence number";
+    ASSERT_TRUE(finish.handled)
+            << "receiveConsumerResponse should have set handled to consumer's reply";
+    ASSERT_GE(finish.consumeTime, publishTime)
             << "finished signal's consume time should be greater than publish time";
 }
 
@@ -343,13 +349,15 @@
     status = mConsumer->sendFinishedSignal(seq, true);
     ASSERT_EQ(OK, status) << "consumer sendFinishedSignal should return OK";
 
-    android::base::Result<InputPublisher::Finished> result = mPublisher->receiveFinishedSignal();
-    ASSERT_TRUE(result.ok()) << "publisher receiveFinishedSignal should return OK";
-    ASSERT_EQ(seq, result->seq)
-            << "receiveFinishedSignal should have returned the original sequence number";
-    ASSERT_TRUE(result->handled)
-            << "receiveFinishedSignal should have set handled to consumer's reply";
-    ASSERT_GE(result->consumeTime, publishTime)
+    Result<InputPublisher::ConsumerResponse> result = mPublisher->receiveConsumerResponse();
+    ASSERT_TRUE(result.ok()) << "receiveConsumerResponse should return OK";
+    ASSERT_TRUE(std::holds_alternative<InputPublisher::Finished>(*result));
+    const InputPublisher::Finished& finish = std::get<InputPublisher::Finished>(*result);
+    ASSERT_EQ(seq, finish.seq)
+            << "receiveConsumerResponse should have returned the original sequence number";
+    ASSERT_TRUE(finish.handled)
+            << "receiveConsumerResponse should have set handled to consumer's reply";
+    ASSERT_GE(finish.consumeTime, publishTime)
             << "finished signal's consume time should be greater than publish time";
 }
 
@@ -385,16 +393,34 @@
     status = mConsumer->sendFinishedSignal(seq, true);
     ASSERT_EQ(OK, status) << "consumer sendFinishedSignal should return OK";
 
-    android::base::Result<InputPublisher::Finished> result = mPublisher->receiveFinishedSignal();
-    ASSERT_TRUE(result.ok()) << "publisher receiveFinishedSignal should return OK";
-    ASSERT_EQ(seq, result->seq)
-            << "publisher receiveFinishedSignal should have returned the original sequence number";
-    ASSERT_TRUE(result->handled)
-            << "publisher receiveFinishedSignal should have set handled to consumer's reply";
-    ASSERT_GE(result->consumeTime, publishTime)
+    Result<InputPublisher::ConsumerResponse> result = mPublisher->receiveConsumerResponse();
+    ASSERT_TRUE(result.ok()) << "receiveConsumerResponse should return OK";
+    ASSERT_TRUE(std::holds_alternative<InputPublisher::Finished>(*result));
+    const InputPublisher::Finished& finish = std::get<InputPublisher::Finished>(*result);
+    ASSERT_EQ(seq, finish.seq)
+            << "receiveConsumerResponse should have returned the original sequence number";
+    ASSERT_TRUE(finish.handled)
+            << "receiveConsumerResponse should have set handled to consumer's reply";
+    ASSERT_GE(finish.consumeTime, publishTime)
             << "finished signal's consume time should be greater than publish time";
 }
 
+TEST_F(InputPublisherAndConsumerTest, SendTimeline) {
+    const int32_t inputEventId = 20;
+    std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
+    graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 30;
+    graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = 40;
+    status_t status = mConsumer->sendTimeline(inputEventId, graphicsTimeline);
+    ASSERT_EQ(OK, status);
+
+    Result<InputPublisher::ConsumerResponse> result = mPublisher->receiveConsumerResponse();
+    ASSERT_TRUE(result.ok()) << "receiveConsumerResponse should return OK";
+    ASSERT_TRUE(std::holds_alternative<InputPublisher::Timeline>(*result));
+    const InputPublisher::Timeline& timeline = std::get<InputPublisher::Timeline>(*result);
+    ASSERT_EQ(inputEventId, timeline.inputEventId);
+    ASSERT_EQ(graphicsTimeline, timeline.graphicsTimeline);
+}
+
 TEST_F(InputPublisherAndConsumerTest, PublishKeyEvent_EndToEnd) {
     ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
 }
diff --git a/libs/input/tests/StructLayout_test.cpp b/libs/input/tests/StructLayout_test.cpp
index 3d80b38..585779e 100644
--- a/libs/input/tests/StructLayout_test.cpp
+++ b/libs/input/tests/StructLayout_test.cpp
@@ -96,6 +96,10 @@
   CHECK_OFFSET(InputMessage::Body::Finished, handled, 0);
   CHECK_OFFSET(InputMessage::Body::Finished, empty, 1);
   CHECK_OFFSET(InputMessage::Body::Finished, consumeTime, 8);
+
+  CHECK_OFFSET(InputMessage::Body::Timeline, eventId, 0);
+  CHECK_OFFSET(InputMessage::Body::Timeline, empty, 4);
+  CHECK_OFFSET(InputMessage::Body::Timeline, graphicsTimeline, 8);
 }
 
 void TestHeaderSize() {
@@ -117,6 +121,9 @@
     static_assert(sizeof(InputMessage::Body::Focus) == 8);
     static_assert(sizeof(InputMessage::Body::Capture) == 8);
     static_assert(sizeof(InputMessage::Body::Drag) == 16);
+    // Timeline
+    static_assert(GraphicsTimeline::SIZE == 2);
+    static_assert(sizeof(InputMessage::Body::Timeline) == 24);
 }
 
 // --- VerifiedInputEvent ---
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index b406a9c..ada689a 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -159,8 +159,8 @@
 }
 
 int32_t ANativeWindow_setFrameRate(ANativeWindow* window, float frameRate, int8_t compatibility) {
-    return ANativeWindow_setFrameRateWithSeamlessness(window, frameRate, compatibility,
-        /*shouldBeSeamless*/ true);
+    return ANativeWindow_setFrameRateWithChangeStrategy(window, frameRate, compatibility,
+        ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
 }
 
 void ANativeWindow_tryAllocateBuffers(ANativeWindow* window) {
@@ -170,12 +170,12 @@
     window->perform(window, NATIVE_WINDOW_ALLOCATE_BUFFERS);
 }
 
-int32_t ANativeWindow_setFrameRateWithSeamlessness(ANativeWindow* window, float frameRate,
-        int8_t compatibility, bool shouldBeSeamless) {
+int32_t ANativeWindow_setFrameRateWithChangeStrategy(ANativeWindow* window, float frameRate,
+        int8_t compatibility, int8_t changeFrameRateStrategy) {
     if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) {
         return -EINVAL;
     }
-    return native_window_set_frame_rate(window, frameRate, compatibility, shouldBeSeamless);
+    return native_window_set_frame_rate(window, frameRate, compatibility, changeFrameRateStrategy);
 }
 
 /**************************************************************************************************
diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h
index 285f2fb..50e9d53 100644
--- a/libs/nativewindow/include/android/native_window.h
+++ b/libs/nativewindow/include/android/native_window.h
@@ -247,9 +247,10 @@
 };
 
 /**
- * Same as ANativeWindow_setFrameRateWithSeamlessness(window, frameRate, compatibility, true).
+ * Same as ANativeWindow_setFrameRateWithChangeStrategy(window, frameRate, compatibility,
+ * ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS).
  *
- * See ANativeWindow_setFrameRateWithSeamlessness().
+ * See ANativeWindow_setFrameRateWithChangeStrategy().
  *
  * Available since API level 30.
  */
@@ -267,6 +268,19 @@
  */
 void ANativeWindow_tryAllocateBuffers(ANativeWindow* window) __INTRODUCED_IN(30);
 
+/** Change frame rate strategy value for ANativeWindow_setFrameRate. */
+enum ANativeWindow_ChangeFrameRateStrategy {
+    /**
+     * Change the frame rate only if the transition is going to be seamless.
+     */
+    ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS = 0,
+    /**
+     * Change the frame rate even if the transition is going to be non-seamless,
+     * i.e. with visual interruptions for the user.
+     */
+    ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS = 1
+} __INTRODUCED_IN(31);
+
 /**
  * Sets the intended frame rate for this window.
  *
@@ -296,17 +310,16 @@
  * 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.
+ * \param changeFrameRateStrategy 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. See the ANATIVEWINDOW_CHANGE_FRAME_RATE_* values.
  *
  * \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);
+int32_t ANativeWindow_setFrameRateWithChangeStrategy(ANativeWindow* window, float frameRate,
+        int8_t compatibility, int8_t changeFrameRateStrategy)
+        __INTRODUCED_IN(31);
 
 #ifdef __cplusplus
 };
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index 7aa2cf4..cc82bb4 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1019,9 +1019,9 @@
 }
 
 static inline int native_window_set_frame_rate(struct ANativeWindow* window, float frameRate,
-                                        int8_t compatibility, bool shouldBeSeamless) {
+                                        int8_t compatibility, int8_t changeFrameRateStrategy) {
     return window->perform(window, NATIVE_WINDOW_SET_FRAME_RATE, (double)frameRate,
-                           (int)compatibility, (int)shouldBeSeamless);
+                           (int)compatibility, (int)changeFrameRateStrategy);
 }
 
 static inline int native_window_set_frame_timeline_info(struct ANativeWindow* window,
diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt
index 24d0e3b..988132c 100644
--- a/libs/nativewindow/libnativewindow.map.txt
+++ b/libs/nativewindow/libnativewindow.map.txt
@@ -47,7 +47,7 @@
     ANativeWindow_setBuffersTransform;
     ANativeWindow_setDequeueTimeout; # apex # introduced=30
     ANativeWindow_setFrameRate; # introduced=30
-    ANativeWindow_setFrameRateWithSeamlessness; # introduced=31
+    ANativeWindow_setFrameRateWithChangeStrategy; # introduced=31
     ANativeWindow_setSharedBufferMode; # llndk
     ANativeWindow_setSwapInterval; # llndk
     ANativeWindow_setUsage; # llndk
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 6977396..073455a 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -3312,15 +3312,21 @@
             bool gotOne = false;
             status_t status = OK;
             for (;;) {
-                Result<InputPublisher::Finished> result =
-                        connection->inputPublisher.receiveFinishedSignal();
+                Result<InputPublisher::ConsumerResponse> result =
+                        connection->inputPublisher.receiveConsumerResponse();
                 if (!result.ok()) {
                     status = result.error().code();
                     break;
                 }
-                const InputPublisher::Finished& finished = *result;
-                d->finishDispatchCycleLocked(currentTime, connection, finished.seq,
-                                             finished.handled, finished.consumeTime);
+
+                if (std::holds_alternative<InputPublisher::Finished>(*result)) {
+                    const InputPublisher::Finished& finish =
+                            std::get<InputPublisher::Finished>(*result);
+                    d->finishDispatchCycleLocked(currentTime, connection, finish.seq,
+                                                 finish.handled, finish.consumeTime);
+                } else if (std::holds_alternative<InputPublisher::Timeline>(*result)) {
+                    // TODO(b/167947340): Report this data to LatencyTracker
+                }
                 gotOne = true;
             }
             if (gotOne) {
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 76f6e74..b62fce4 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -742,6 +742,11 @@
         ASSERT_EQ(OK, status) << mName.c_str() << ": consumer sendFinishedSignal should return OK.";
     }
 
+    void sendTimeline(int32_t inputEventId, std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) {
+        const status_t status = mConsumer->sendTimeline(inputEventId, timeline);
+        ASSERT_EQ(OK, status);
+    }
+
     void consumeEvent(int32_t expectedEventType, int32_t expectedAction,
                       std::optional<int32_t> expectedDisplayId,
                       std::optional<int32_t> expectedFlags) {
@@ -1054,6 +1059,11 @@
         mInputReceiver->finishEvent(sequenceNum);
     }
 
+    void sendTimeline(int32_t inputEventId, std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) {
+        ASSERT_NE(mInputReceiver, nullptr) << "Invalid receive event on window with no receiver";
+        mInputReceiver->sendTimeline(inputEventId, timeline);
+    }
+
     InputEvent* consume() {
         if (mInputReceiver == nullptr) {
             return nullptr;
@@ -1970,6 +1980,21 @@
     secondWindow->assertNoEvents();
 }
 
+TEST_F(InputDispatcherTest, SendTimeline_DoesNotCrashDispatcher) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
+    graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 2;
+    graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = 3;
+
+    window->sendTimeline(1 /*inputEventId*/, graphicsTimeline);
+    window->assertNoEvents();
+    mDispatcher->waitForIdle();
+}
+
 class FakeMonitorReceiver {
 public:
     FakeMonitorReceiver(const sp<InputDispatcher>& dispatcher, const std::string name,
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index 8f1aef0..be9bce0 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -329,6 +329,37 @@
     mRefreshPending = false;
     return hasReadyFrame();
 }
+namespace {
+TimeStats::SetFrameRateVote frameRateToSetFrameRateVotePayload(Layer::FrameRate frameRate) {
+    using FrameRateCompatibility = TimeStats::SetFrameRateVote::FrameRateCompatibility;
+    using Seamlessness = TimeStats::SetFrameRateVote::Seamlessness;
+    const auto frameRateCompatibility = [frameRate] {
+        switch (frameRate.type) {
+            case Layer::FrameRateCompatibility::Default:
+                return FrameRateCompatibility::Default;
+            case Layer::FrameRateCompatibility::ExactOrMultiple:
+                return FrameRateCompatibility::ExactOrMultiple;
+            default:
+                return FrameRateCompatibility::Undefined;
+        }
+    }();
+
+    const auto seamlessness = [frameRate] {
+        switch (frameRate.seamlessness) {
+            case scheduler::Seamlessness::OnlySeamless:
+                return Seamlessness::ShouldBeSeamless;
+            case scheduler::Seamlessness::SeamedAndSeamless:
+                return Seamlessness::NotRequired;
+            default:
+                return Seamlessness::Undefined;
+        }
+    }();
+
+    return TimeStats::SetFrameRateVote{.frameRate = frameRate.rate.getValue(),
+                                       .frameRateCompatibility = frameRateCompatibility,
+                                       .seamlessness = seamlessness};
+}
+} // namespace
 
 bool BufferLayer::onPostComposition(const DisplayDevice* display,
                                     const std::shared_ptr<FenceTime>& glDoneFence,
@@ -381,7 +412,9 @@
     const std::optional<Fps> renderRate = mFlinger->mScheduler->getFrameRateOverride(getOwnerUid());
     if (presentFence->isValid()) {
         mFlinger->mTimeStats->setPresentFence(layerId, mCurrentFrameNumber, presentFence,
-                                              refreshRate, renderRate);
+                                              refreshRate, renderRate,
+                                              frameRateToSetFrameRateVotePayload(
+                                                      mDrawingState.frameRate));
         mFlinger->mFrameTracer->traceFence(layerId, getCurrentBufferId(), mCurrentFrameNumber,
                                            presentFence, FrameTracer::FrameEvent::PRESENT_FENCE);
         mFrameTracker.setActualPresentFence(std::shared_ptr<FenceTime>(presentFence));
@@ -393,7 +426,9 @@
         // timestamp instead.
         const nsecs_t actualPresentTime = display->getRefreshTimestamp();
         mFlinger->mTimeStats->setPresentTime(layerId, mCurrentFrameNumber, actualPresentTime,
-                                             refreshRate, renderRate);
+                                             refreshRate, renderRate,
+                                             frameRateToSetFrameRateVotePayload(
+                                                     mDrawingState.frameRate));
         mFlinger->mFrameTracer->traceTimestamp(layerId, getCurrentBufferId(), mCurrentFrameNumber,
                                                actualPresentTime,
                                                FrameTracer::FrameEvent::PRESENT_FENCE);
diff --git a/services/surfaceflinger/ClientCache.cpp b/services/surfaceflinger/ClientCache.cpp
index 2ae49fa..44b33ef 100644
--- a/services/surfaceflinger/ClientCache.cpp
+++ b/services/surfaceflinger/ClientCache.cpp
@@ -48,7 +48,7 @@
 
     auto bufItr = processBuffers.find(id);
     if (bufItr == processBuffers.end()) {
-        ALOGE("failed to get buffer, invalid buffer id");
+        ALOGV("failed to get buffer, invalid buffer id");
         return false;
     }
 
@@ -150,7 +150,7 @@
 
     ClientCacheBuffer* buf = nullptr;
     if (!getBuffer(cacheId, &buf)) {
-        ALOGE("failed to register erased recipient, could not retrieve buffer");
+        ALOGV("failed to register erased recipient, could not retrieve buffer");
         return false;
     }
     buf->recipients.insert(recipient);
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
index a56f28a..cfb4962 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <compositionengine/Output.h>
+#include <compositionengine/ProjectionSpace.h>
 #include <compositionengine/impl/planner/LayerState.h>
 #include <renderengine/RenderEngine.h>
 
@@ -92,8 +94,8 @@
     }
     void incrementAge() { ++mAge; }
 
-    // Renders the cached set with the supplied output dataspace.
-    void render(renderengine::RenderEngine&, ui::Dataspace outputDataspace);
+    // Renders the cached set with the supplied output composition state.
+    void render(renderengine::RenderEngine& re, const OutputCompositionState& outputState);
 
     void dump(std::string& result) const;
 
@@ -133,6 +135,7 @@
     Texture mTexture;
     sp<Fence> mDrawFence;
     ui::Dataspace mOutputDataspace;
+    ui::Transform::RotationFlags mOrientation = ui::Transform::ROT_0;
 
     static const bool sDebugHighlighLayers;
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
index 2bd3249..e21b5bc 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
@@ -42,8 +42,9 @@
     NonBufferHash flattenLayers(const std::vector<const LayerState*>& layers, NonBufferHash,
                                 std::chrono::steady_clock::time_point now);
 
-    // Renders the newest cached sets with the supplied output dataspace
-    void renderCachedSets(renderengine::RenderEngine&, ui::Dataspace outputDataspace);
+    // Renders the newest cached sets with the supplied output composition state
+    void renderCachedSets(renderengine::RenderEngine& re,
+                          const OutputCompositionState& outputState);
 
     void dump(std::string& result) const;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
index 89de34d..e6d2b63 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
@@ -59,7 +59,8 @@
             compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers);
 
     // The planner will call to the Flattener to render any pending cached set
-    void renderCachedSets(renderengine::RenderEngine&, ui::Dataspace outputDataspace);
+    void renderCachedSets(renderengine::RenderEngine& re,
+                          const OutputCompositionState& outputState);
 
     void dump(const Vector<String16>& args, std::string&);
 
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index ded2dcc..aed3be9 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -1252,7 +1252,7 @@
 
 void Output::renderCachedSets() {
     if (mPlanner) {
-        mPlanner->renderCachedSets(getCompositionEngine().getRenderEngine(), getState().dataspace);
+        mPlanner->renderCachedSets(getCompositionEngine().getRenderEngine(), getState());
     }
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index b364649..e662491 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -354,6 +354,7 @@
 
     Rect displayFrame = outputDependentState.displayFrame;
     FloatRect sourceCrop = outputDependentState.sourceCrop;
+
     if (outputDependentState.overrideInfo.buffer != nullptr) { // adyabr
         displayFrame = outputDependentState.overrideInfo.displayFrame;
         sourceCrop = displayFrame.toFloatRect();
@@ -431,11 +432,13 @@
         outputDependentState.outputSpaceVisibleRegion.dump(LOG_TAG);
     }
 
-    if (auto error = hwcLayer->setDataspace(outputDependentState.dataspace);
-        error != hal::Error::NONE) {
-        ALOGE("[%s] Failed to set dataspace %d: %s (%d)", getLayerFE().getDebugName(),
-              outputDependentState.dataspace, to_string(error).c_str(),
-              static_cast<int32_t>(error));
+    const auto dataspace = outputDependentState.overrideInfo.buffer
+            ? outputDependentState.overrideInfo.dataspace
+            : outputDependentState.dataspace;
+
+    if (auto error = hwcLayer->setDataspace(dataspace); error != hal::Error::NONE) {
+        ALOGE("[%s] Failed to set dataspace %d: %s (%d)", getLayerFE().getDebugName(), dataspace,
+              to_string(error).c_str(), static_cast<int32_t>(error));
     }
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index 4d3036a..e8c6de0 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -19,6 +19,7 @@
 // #define LOG_NDEBUG 0
 
 #include <android-base/properties.h>
+#include <compositionengine/impl/OutputCompositionState.h>
 #include <compositionengine/impl/planner/CachedSet.h>
 #include <math/HashCombine.h>
 #include <renderengine/DisplaySettings.h>
@@ -84,6 +85,7 @@
     size_t hash = 0;
     android::hashCombineSingle(hash, mBounds);
     android::hashCombineSingle(hash, mOutputDataspace);
+    android::hashCombineSingle(hash, mOrientation);
     return hash;
 }
 
@@ -148,10 +150,15 @@
     }
 }
 
-void CachedSet::render(renderengine::RenderEngine& renderEngine, ui::Dataspace outputDataspace) {
+void CachedSet::render(renderengine::RenderEngine& renderEngine,
+                       const OutputCompositionState& outputState) {
+    const ui::Dataspace& outputDataspace = outputState.dataspace;
+    const ui::Transform::RotationFlags orientation =
+            ui::Transform::toRotationFlags(outputState.displaySpace.orientation);
     renderengine::DisplaySettings displaySettings{
             .physicalDisplay = Rect(0, 0, mBounds.getWidth(), mBounds.getHeight()),
             .clip = mBounds,
+            .orientation = orientation,
             .outputDataspace = outputDataspace,
     };
 
@@ -217,6 +224,7 @@
         mTexture.setBuffer(buffer, &renderEngine);
         mDrawFence = new Fence(drawFence.release());
         mOutputDataspace = outputDataspace;
+        mOrientation = orientation;
     }
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index 60ebbb2..06f7e7a 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -52,12 +52,12 @@
 }
 
 void Flattener::renderCachedSets(renderengine::RenderEngine& renderEngine,
-                                 ui::Dataspace outputDataspace) {
+                                 const OutputCompositionState& outputState) {
     if (!mNewCachedSet) {
         return;
     }
 
-    mNewCachedSet->render(renderEngine, outputDataspace);
+    mNewCachedSet->render(renderEngine, outputState);
 }
 
 void Flattener::dump(std::string& result) const {
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
index 87721c7..ad75557 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
@@ -134,8 +134,8 @@
 }
 
 void Planner::renderCachedSets(renderengine::RenderEngine& renderEngine,
-                               ui::Dataspace outputDataspace) {
-    mFlattener.renderCachedSets(renderEngine, outputDataspace);
+                               const OutputCompositionState& outputState) {
+    mFlattener.renderCachedSets(renderEngine, outputState);
 }
 
 void Planner::dump(const Vector<String16>& args, std::string& result) {
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index 9dd199d..83fb9e3 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -694,6 +694,7 @@
             static_cast<Hwc2::IComposerClient::BlendMode>(41);
     static constexpr float kAlpha = 51.f;
     static constexpr ui::Dataspace kDataspace = static_cast<ui::Dataspace>(71);
+    static constexpr ui::Dataspace kOverrideDataspace = static_cast<ui::Dataspace>(72);
     static constexpr int kSupportedPerFrameMetadata = 101;
     static constexpr int kExpectedHwcSlot = 0;
     static constexpr bool kLayerGenericMetadata1Mandatory = true;
@@ -701,13 +702,16 @@
 
     static const half4 kColor;
     static const Rect kDisplayFrame;
+    static const Rect kOverrideDisplayFrame;
     static const Region kOutputSpaceVisibleRegion;
     static const mat4 kColorTransform;
     static const Region kSurfaceDamage;
     static const HdrMetadata kHdrMetadata;
     static native_handle_t* kSidebandStreamHandle;
     static const sp<GraphicBuffer> kBuffer;
+    static const sp<GraphicBuffer> kOverrideBuffer;
     static const sp<Fence> kFence;
+    static const sp<Fence> kOverrideFence;
     static const std::string kLayerGenericMetadata1Key;
     static const std::vector<uint8_t> kLayerGenericMetadata1Value;
     static const std::string kLayerGenericMetadata2Key;
@@ -751,9 +755,19 @@
                                                              kLayerGenericMetadata2Value};
     }
 
-    void expectGeometryCommonCalls() {
-        EXPECT_CALL(*mHwcLayer, setDisplayFrame(kDisplayFrame)).WillOnce(Return(kError));
-        EXPECT_CALL(*mHwcLayer, setSourceCrop(kSourceCrop)).WillOnce(Return(kError));
+    void includeOverrideInfo() {
+        auto& overrideInfo = mOutputLayer.editState().overrideInfo;
+
+        overrideInfo.buffer = kOverrideBuffer;
+        overrideInfo.acquireFence = kOverrideFence;
+        overrideInfo.displayFrame = kOverrideDisplayFrame;
+        overrideInfo.dataspace = kOverrideDataspace;
+    }
+
+    void expectGeometryCommonCalls(Rect displayFrame = kDisplayFrame,
+                                   FloatRect sourceCrop = kSourceCrop) {
+        EXPECT_CALL(*mHwcLayer, setDisplayFrame(displayFrame)).WillOnce(Return(kError));
+        EXPECT_CALL(*mHwcLayer, setSourceCrop(sourceCrop)).WillOnce(Return(kError));
         EXPECT_CALL(*mHwcLayer, setZOrder(kZOrder)).WillOnce(Return(kError));
         EXPECT_CALL(*mHwcLayer, setTransform(kBufferTransform)).WillOnce(Return(kError));
 
@@ -761,10 +775,11 @@
         EXPECT_CALL(*mHwcLayer, setPlaneAlpha(kAlpha)).WillOnce(Return(kError));
     }
 
-    void expectPerFrameCommonCalls(SimulateUnsupported unsupported = SimulateUnsupported::None) {
+    void expectPerFrameCommonCalls(SimulateUnsupported unsupported = SimulateUnsupported::None,
+                                   ui::Dataspace dataspace = kDataspace) {
         EXPECT_CALL(*mHwcLayer, setVisibleRegion(RegionEq(kOutputSpaceVisibleRegion)))
                 .WillOnce(Return(kError));
-        EXPECT_CALL(*mHwcLayer, setDataspace(kDataspace)).WillOnce(Return(kError));
+        EXPECT_CALL(*mHwcLayer, setDataspace(dataspace)).WillOnce(Return(kError));
         EXPECT_CALL(*mHwcLayer, setColorTransform(kColorTransform))
                 .WillOnce(Return(unsupported == SimulateUnsupported::ColorTransform
                                          ? hal::Error::UNSUPPORTED
@@ -793,9 +808,10 @@
         EXPECT_CALL(*mHwcLayer, setSidebandStream(kSidebandStreamHandle));
     }
 
-    void expectSetHdrMetadataAndBufferCalls() {
+    void expectSetHdrMetadataAndBufferCalls(sp<GraphicBuffer> buffer = kBuffer,
+                                            sp<Fence> fence = kFence) {
         EXPECT_CALL(*mHwcLayer, setPerFrameMetadata(kSupportedPerFrameMetadata, kHdrMetadata));
-        EXPECT_CALL(*mHwcLayer, setBuffer(kExpectedHwcSlot, kBuffer, kFence));
+        EXPECT_CALL(*mHwcLayer, setBuffer(kExpectedHwcSlot, buffer, fence));
     }
 
     void expectGenericLayerMetadataCalls() {
@@ -817,6 +833,7 @@
 const half4 OutputLayerWriteStateToHWCTest::kColor{81.f / 255.f, 82.f / 255.f, 83.f / 255.f,
                                                    84.f / 255.f};
 const Rect OutputLayerWriteStateToHWCTest::kDisplayFrame{1001, 1002, 1003, 10044};
+const Rect OutputLayerWriteStateToHWCTest::kOverrideDisplayFrame{1002, 1003, 1004, 20044};
 const Region OutputLayerWriteStateToHWCTest::kOutputSpaceVisibleRegion{
         Rect{1005, 1006, 1007, 1008}};
 const mat4 OutputLayerWriteStateToHWCTest::kColorTransform{
@@ -828,7 +845,9 @@
 native_handle_t* OutputLayerWriteStateToHWCTest::kSidebandStreamHandle =
         reinterpret_cast<native_handle_t*>(1031);
 const sp<GraphicBuffer> OutputLayerWriteStateToHWCTest::kBuffer;
+const sp<GraphicBuffer> OutputLayerWriteStateToHWCTest::kOverrideBuffer = new GraphicBuffer();
 const sp<Fence> OutputLayerWriteStateToHWCTest::kFence;
+const sp<Fence> OutputLayerWriteStateToHWCTest::kOverrideFence = new Fence();
 const std::string OutputLayerWriteStateToHWCTest::kLayerGenericMetadata1Key =
         "com.example.metadata.1";
 const std::vector<uint8_t> OutputLayerWriteStateToHWCTest::kLayerGenericMetadata1Value{{1, 2, 3}};
@@ -983,6 +1002,18 @@
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
+TEST_F(OutputLayerWriteStateToHWCTest, includesOverrideInfoIfPresent) {
+    mLayerFEState.compositionType = Hwc2::IComposerClient::Composition::DEVICE;
+    includeOverrideInfo();
+
+    expectGeometryCommonCalls(kOverrideDisplayFrame, kOverrideDisplayFrame.toFloatRect());
+    expectPerFrameCommonCalls(SimulateUnsupported::None, kOverrideDataspace);
+    expectSetHdrMetadataAndBufferCalls(kOverrideBuffer, kOverrideFence);
+    expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
+
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false);
+}
+
 /*
  * OutputLayer::writeCursorPositionToHWC()
  */
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
index 7842efb..0e7ef71 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <compositionengine/impl/OutputCompositionState.h>
 #include <compositionengine/impl/planner/CachedSet.h>
 #include <compositionengine/impl/planner/LayerState.h>
 #include <compositionengine/mock/LayerFE.h>
@@ -59,6 +60,7 @@
 
     static constexpr size_t kNumLayers = 5;
     std::vector<std::unique_ptr<TestLayer>> mTestLayers;
+    impl::OutputCompositionState mOutputState;
 
     android::renderengine::mock::RenderEngine mRenderEngine;
 };
@@ -87,6 +89,10 @@
                 std::make_unique<CachedSet::Layer>(testLayer->layerState.get(), kStartTime);
 
         mTestLayers.emplace_back(std::move(testLayer));
+
+        // set up minimium params needed for rendering
+        mOutputState.dataspace = ui::Dataspace::SRGB;
+        mOutputState.displaySpace.orientation = ui::ROTATION_0;
     }
 }
 
@@ -300,7 +306,7 @@
     EXPECT_CALL(*layerFE2, prepareClientCompositionList(_)).WillOnce(Return(clientCompList2));
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Invoke(drawLayers));
     EXPECT_CALL(mRenderEngine, cacheExternalTextureBuffer(_));
-    cachedSet.render(mRenderEngine, ui::Dataspace::SRGB);
+    cachedSet.render(mRenderEngine, mOutputState);
     expectReadyBuffer(cachedSet);
 
     // Now check that appending a new cached set properly cleans up RenderEngine resources.
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index bd77559..211b4dc 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <compositionengine/impl/OutputCompositionState.h>
 #include <compositionengine/impl/planner/CachedSet.h>
 #include <compositionengine/impl/planner/Flattener.h>
 #include <compositionengine/impl/planner/LayerState.h>
@@ -76,6 +77,7 @@
 
     static constexpr size_t kNumLayers = 5;
     std::vector<std::unique_ptr<TestLayer>> mTestLayers;
+    impl::OutputCompositionState mOutputState;
 };
 
 void FlattenerTest::SetUp() {
@@ -120,6 +122,10 @@
         testLayer->layerState->incrementFramesSinceBufferUpdate();
 
         mTestLayers.emplace_back(std::move(testLayer));
+
+        // set up minimium params needed for rendering
+        mOutputState.dataspace = ui::Dataspace::SRGB;
+        mOutputState.displaySpace.orientation = ui::ROTATION_0;
     }
 }
 
@@ -134,13 +140,13 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, ui::Dataspace::SRGB);
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
 
     // same geometry, update the internal layer stack
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, ui::Dataspace::SRGB);
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
 }
 
 void FlattenerTest::expectAllLayersFlattened(const std::vector<const LayerState*>& layers) {
@@ -150,7 +156,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, ui::Dataspace::SRGB);
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
 
     for (const auto layer : layers) {
         EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
@@ -160,7 +166,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, ui::Dataspace::SRGB);
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
 
     const auto buffer = layers[0]->getOutputLayer()->getState().overrideInfo.buffer;
     EXPECT_NE(nullptr, buffer);
@@ -195,7 +201,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, ui::Dataspace::SRGB);
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
 }
 
 TEST_F(FlattenerTest, flattenLayers_basicFlatten) {
@@ -241,7 +247,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, ui::Dataspace::SRGB);
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -276,7 +282,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, ui::Dataspace::SRGB);
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
 
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_EQ(nullptr, overrideBuffer2);
@@ -313,7 +319,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, ui::Dataspace::SRGB);
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
 
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_EQ(nullptr, overrideBuffer2);
@@ -322,7 +328,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, ui::Dataspace::SRGB);
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
 
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_NE(nullptr, overrideBuffer2);
@@ -335,7 +341,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, ui::Dataspace::SRGB);
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
 
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_NE(nullptr, overrideBuffer2);
@@ -344,7 +350,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, ui::Dataspace::SRGB);
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -386,7 +392,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, ui::Dataspace::SRGB);
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
 
     EXPECT_EQ(nullptr, overrideBuffer1);
     EXPECT_EQ(nullptr, overrideBuffer2);
@@ -399,7 +405,8 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, ui::Dataspace::SRGB);
+    mOutputState.displaySpace.orientation = ui::ROTATION_90;
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -411,7 +418,8 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, ui::Dataspace::SRGB);
+    mOutputState.displaySpace.orientation = ui::ROTATION_180;
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -426,7 +434,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, ui::Dataspace::SRGB);
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -437,7 +445,8 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mFlattener->renderCachedSets(mRenderEngine, ui::Dataspace::SRGB);
+    mOutputState.displaySpace.orientation = ui::ROTATION_270;
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
 
     EXPECT_NE(nullptr, overrideBuffer1);
     EXPECT_EQ(overrideBuffer1, overrideBuffer2);
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index 2784861..6485265 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -575,13 +575,9 @@
             }
         } else if (mFrameReadyMetadata == FrameReadyMetadata::LateFinish) {
             // Finish late, Present late
-            if (displayFrameJankType == JankType::None) {
-                // Display frame is not janky, so purely app's fault
-                mJankType |= JankType::AppDeadlineMissed;
-            } else {
-                // Propagate DisplayFrame's jankType if it is janky
-                mJankType |= displayFrameJankType;
-            }
+            mJankType |= JankType::AppDeadlineMissed;
+            // Propagate DisplayFrame's jankType if it is janky
+            mJankType |= displayFrameJankType;
         }
     }
 }
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index cd3e8ad..b5410fe 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -2867,6 +2867,18 @@
     }
 }
 
+scheduler::Seamlessness Layer::FrameRate::convertChangeFrameRateStrategy(int8_t strategy) {
+    switch (strategy) {
+        case ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS:
+            return Seamlessness::OnlySeamless;
+        case ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS:
+            return Seamlessness::SeamedAndSeamless;
+        default:
+            LOG_ALWAYS_FATAL("Invalid change frame sate strategy value %d", strategy);
+            return Seamlessness::Default;
+    }
+}
+
 bool Layer::getPrimaryDisplayOnly() const {
     const State& s(mDrawingState);
     if (s.flags & layer_state_t::eLayerSkipScreenshot) {
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 1c5d6ec..421a107 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -168,8 +168,9 @@
               : rate(0),
                 type(FrameRateCompatibility::Default),
                 seamlessness(Seamlessness::Default) {}
-        FrameRate(Fps rate, FrameRateCompatibility type, bool shouldBeSeamless = true)
-              : rate(rate), type(type), seamlessness(getSeamlessness(rate, shouldBeSeamless)) {}
+        FrameRate(Fps rate, FrameRateCompatibility type,
+                  Seamlessness seamlessness = Seamlessness::OnlySeamless)
+              : rate(rate), type(type), seamlessness(getSeamlessness(rate, seamlessness)) {}
 
         bool operator==(const FrameRate& other) const {
             return rate.equalsWithMargin(other.rate) && type == other.type &&
@@ -181,18 +182,16 @@
         // Convert an ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_* value to a
         // Layer::FrameRateCompatibility. Logs fatal if the compatibility value is invalid.
         static FrameRateCompatibility convertCompatibility(int8_t compatibility);
+        static scheduler::Seamlessness convertChangeFrameRateStrategy(int8_t strategy);
 
     private:
-        static Seamlessness getSeamlessness(Fps rate, bool shouldBeSeamless) {
+        static Seamlessness getSeamlessness(Fps rate, Seamlessness seamlessness) {
             if (!rate.isValid()) {
                 // Refresh rate of 0 is a special value which should reset the vote to
                 // its default value.
                 return Seamlessness::Default;
-            } else if (shouldBeSeamless) {
-                return Seamlessness::OnlySeamless;
-            } else {
-                return Seamlessness::SeamedAndSeamless;
             }
+            return seamlessness;
         }
     };
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index a5cf797..b062acd 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -215,7 +215,7 @@
     int explicitExactOrMultipleVoteLayers = 0;
     int explicitExact = 0;
     float maxExplicitWeight = 0;
-    int seamedLayers = 0;
+    int seamedFocusedLayers = 0;
     for (const auto& layer : layers) {
         switch (layer.vote) {
             case LayerVoteType::NoVote:
@@ -243,8 +243,8 @@
                 break;
         }
 
-        if (layer.seamlessness == Seamlessness::SeamedAndSeamless) {
-            seamedLayers++;
+        if (layer.seamlessness == Seamlessness::SeamedAndSeamless && layer.focused) {
+            seamedFocusedLayers++;
         }
     }
 
@@ -329,12 +329,11 @@
             // mode group otherwise. In second case, if the current mode group is different
             // from the default, this means a layer with seamlessness=SeamedAndSeamless has just
             // disappeared.
-            const bool isInPolicyForDefault = seamedLayers > 0
+            const bool isInPolicyForDefault = seamedFocusedLayers > 0
                     ? scores[i].refreshRate->getModeGroup() == mCurrentRefreshRate->getModeGroup()
                     : scores[i].refreshRate->getModeGroup() == defaultMode->getModeGroup();
 
-            if (layer.seamlessness == Seamlessness::Default && !isInPolicyForDefault &&
-                !layer.focused) {
+            if (layer.seamlessness == Seamlessness::Default && !isInPolicyForDefault) {
                 ALOGV("%s ignores %s. Current mode = %s", formatLayerInfo(layer, weight).c_str(),
                       scores[i].refreshRate->toString().c_str(),
                       mCurrentRefreshRate->toString().c_str());
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index f7a2669..f7c9291 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -3984,13 +3984,16 @@
         }
     }
     if (what & layer_state_t::eFrameRateChanged) {
-        if (ValidateFrameRate(s.frameRate, s.frameRateCompatibility,
-                              "SurfaceFlinger::setClientStateLocked", privileged) &&
-            layer->setFrameRate(Layer::FrameRate(Fps(s.frameRate),
-                                                 Layer::FrameRate::convertCompatibility(
-                                                         s.frameRateCompatibility),
-                                                 s.shouldBeSeamless))) {
-            flags |= eTraversalNeeded;
+        if (ValidateFrameRate(s.frameRate, s.frameRateCompatibility, s.changeFrameRateStrategy,
+                              "SurfaceFlinger::setClientStateLocked", privileged)) {
+            const auto compatibility =
+                    Layer::FrameRate::convertCompatibility(s.frameRateCompatibility);
+            const auto strategy =
+                    Layer::FrameRate::convertChangeFrameRateStrategy(s.changeFrameRateStrategy);
+
+            if (layer->setFrameRate(Layer::FrameRate(Fps(s.frameRate), compatibility, strategy))) {
+                flags |= eTraversalNeeded;
+            }
         }
     }
     if (what & layer_state_t::eFixedTransformHintChanged) {
@@ -6392,8 +6395,9 @@
 }
 
 status_t SurfaceFlinger::setFrameRate(const sp<IGraphicBufferProducer>& surface, float frameRate,
-                                      int8_t compatibility, bool shouldBeSeamless) {
-    if (!ValidateFrameRate(frameRate, compatibility, "SurfaceFlinger::setFrameRate")) {
+                                      int8_t compatibility, int8_t changeFrameRateStrategy) {
+    if (!ValidateFrameRate(frameRate, compatibility, changeFrameRateStrategy,
+                           "SurfaceFlinger::setFrameRate")) {
         return BAD_VALUE;
     }
 
@@ -6405,10 +6409,12 @@
                 ALOGE("Attempt to set frame rate on a layer that no longer exists");
                 return BAD_VALUE;
             }
+            const auto strategy =
+                    Layer::FrameRate::convertChangeFrameRateStrategy(changeFrameRateStrategy);
             if (layer->setFrameRate(
                         Layer::FrameRate(Fps{frameRate},
                                          Layer::FrameRate::convertCompatibility(compatibility),
-                                         shouldBeSeamless))) {
+                                         strategy))) {
                 setTransactionFlags(eTraversalNeeded);
             }
         } else {
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index e4ff6c9..c57b180 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -688,7 +688,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, bool shouldBeSeamless) override;
+                          int8_t compatibility, int8_t changeFrameRateStrategy) override;
     status_t acquireFrameRateFlexibilityToken(sp<IBinder>* outToken) override;
 
     status_t setFrameTimelineInfo(const sp<IGraphicBufferProducer>& surface,
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index 974ae84..2094972 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -89,12 +89,15 @@
     return byteString;
 }
 
-std::string frameRateVoteToProtoByteString(float refreshRate, int frameRateCompatibility,
-                                           int seamlessness) {
+std::string frameRateVoteToProtoByteString(
+        float refreshRate,
+        TimeStats::SetFrameRateVote::FrameRateCompatibility frameRateCompatibility,
+        TimeStats::SetFrameRateVote::Seamlessness seamlessness) {
     util::ProtoOutputStream proto;
     proto.write(android::util::FIELD_TYPE_FLOAT | 1 /* field id */, refreshRate);
-    proto.write(android::util::FIELD_TYPE_ENUM | 2 /* field id */, frameRateCompatibility);
-    proto.write(android::util::FIELD_TYPE_ENUM | 3 /* field id */, seamlessness);
+    proto.write(android::util::FIELD_TYPE_ENUM | 2 /* field id */,
+                static_cast<int>(frameRateCompatibility));
+    proto.write(android::util::FIELD_TYPE_ENUM | 3 /* field id */, static_cast<int>(seamlessness));
 
     std::string byteString;
     proto.serializeToString(&byteString);
@@ -229,7 +232,10 @@
         mStatsDelegate->statsEventWriteInt32(
                 event, layer->displayRefreshRateBucket); // display_refresh_rate_bucket
         mStatsDelegate->statsEventWriteInt32(event, layer->renderRateBucket); // render_rate_bucket
-        std::string frameRateVoteBytes = frameRateVoteToProtoByteString(0.0, 0, 0);
+        std::string frameRateVoteBytes =
+                frameRateVoteToProtoByteString(layer->setFrameRateVote.frameRate,
+                                               layer->setFrameRateVote.frameRateCompatibility,
+                                               layer->setFrameRateVote.seamlessness);
         mStatsDelegate->statsEventWriteByteArray(event, (const uint8_t*)frameRateVoteBytes.c_str(),
                                                  frameRateVoteBytes.size()); // set_frame_rate_vote
         std::string appDeadlineMissedBytes =
@@ -468,8 +474,10 @@
 }
 
 void TimeStats::flushAvailableRecordsToStatsLocked(int32_t layerId, Fps displayRefreshRate,
-                                                   std::optional<Fps> renderRate) {
+                                                   std::optional<Fps> renderRate,
+                                                   SetFrameRateVote frameRateVote) {
     ATRACE_CALL();
+    ALOGV("[%d]-flushAvailableRecordsToStatsLocked", layerId);
 
     LayerRecord& layerRecord = mTimeStatsTracker[layerId];
     TimeRecord& prevTimeRecord = layerRecord.prevTimeRecord;
@@ -501,6 +509,9 @@
                 displayStats.stats[layerKey].uid = uid;
                 displayStats.stats[layerKey].layerName = layerName;
             }
+            if (frameRateVote.frameRate > 0.0f) {
+                displayStats.stats[layerKey].setFrameRateVote = frameRateVote;
+            }
             TimeStatsHelper::TimeStatsLayer& timeStatsLayer = displayStats.stats[layerKey];
             timeStatsLayer.totalFrames++;
             timeStatsLayer.droppedFrames += layerRecord.droppedFrames;
@@ -724,7 +735,8 @@
 }
 
 void TimeStats::setPresentTime(int32_t layerId, uint64_t frameNumber, nsecs_t presentTime,
-                               Fps displayRefreshRate, std::optional<Fps> renderRate) {
+                               Fps displayRefreshRate, std::optional<Fps> renderRate,
+                               SetFrameRateVote frameRateVote) {
     if (!mEnabled.load()) return;
 
     ATRACE_CALL();
@@ -743,12 +755,13 @@
         layerRecord.waitData++;
     }
 
-    flushAvailableRecordsToStatsLocked(layerId, displayRefreshRate, renderRate);
+    flushAvailableRecordsToStatsLocked(layerId, displayRefreshRate, renderRate, frameRateVote);
 }
 
 void TimeStats::setPresentFence(int32_t layerId, uint64_t frameNumber,
                                 const std::shared_ptr<FenceTime>& presentFence,
-                                Fps displayRefreshRate, std::optional<Fps> renderRate) {
+                                Fps displayRefreshRate, std::optional<Fps> renderRate,
+                                SetFrameRateVote frameRateVote) {
     if (!mEnabled.load()) return;
 
     ATRACE_CALL();
@@ -768,7 +781,7 @@
         layerRecord.waitData++;
     }
 
-    flushAvailableRecordsToStatsLocked(layerId, displayRefreshRate, renderRate);
+    flushAvailableRecordsToStatsLocked(layerId, displayRefreshRate, renderRate, frameRateVote);
 }
 
 static const constexpr int32_t kValidJankyReason = JankType::DisplayHAL |
diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h
index fd112b9..a87b7cb 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.h
+++ b/services/surfaceflinger/TimeStats/TimeStats.h
@@ -50,6 +50,8 @@
 
 class TimeStats {
 public:
+    using SetFrameRateVote = TimeStatsHelper::SetFrameRateVote;
+
     virtual ~TimeStats() = default;
 
     // Called once boot has been finished to perform additional capabilities,
@@ -110,10 +112,12 @@
     // SetPresent{Time, Fence} are not expected to be called in the critical
     // rendering path, as they flush prior fences if those fences have fired.
     virtual void setPresentTime(int32_t layerId, uint64_t frameNumber, nsecs_t presentTime,
-                                Fps displayRefreshRate, std::optional<Fps> renderRate) = 0;
+                                Fps displayRefreshRate, std::optional<Fps> renderRate,
+                                SetFrameRateVote frameRateVote) = 0;
     virtual void setPresentFence(int32_t layerId, uint64_t frameNumber,
                                  const std::shared_ptr<FenceTime>& presentFence,
-                                 Fps displayRefreshRate, std::optional<Fps> renderRate) = 0;
+                                 Fps displayRefreshRate, std::optional<Fps> renderRate,
+                                 SetFrameRateVote frameRateVote) = 0;
 
     // Increments janky frames, blamed to the provided {refreshRate, renderRate, uid, layerName}
     // key, with JankMetadata as supplementary reasons for the jank. Because FrameTimeline is the
@@ -307,10 +311,11 @@
     void setAcquireFence(int32_t layerId, uint64_t frameNumber,
                          const std::shared_ptr<FenceTime>& acquireFence) override;
     void setPresentTime(int32_t layerId, uint64_t frameNumber, nsecs_t presentTime,
-                        Fps displayRefreshRate, std::optional<Fps> renderRate) override;
+                        Fps displayRefreshRate, std::optional<Fps> renderRate,
+                        SetFrameRateVote frameRateVote) override;
     void setPresentFence(int32_t layerId, uint64_t frameNumber,
                          const std::shared_ptr<FenceTime>& presentFence, Fps displayRefreshRate,
-                         std::optional<Fps> renderRate) override;
+                         std::optional<Fps> renderRate, SetFrameRateVote frameRateVote) override;
 
     void incrementJankyFrames(const JankyFramesInfo& info) override;
     // Clean up the layer record
@@ -334,7 +339,8 @@
     AStatsManager_PullAtomCallbackReturn populateLayerAtom(AStatsEventList* data);
     bool recordReadyLocked(int32_t layerId, TimeRecord* timeRecord);
     void flushAvailableRecordsToStatsLocked(int32_t layerId, Fps displayRefreshRate,
-                                            std::optional<Fps> renderRate);
+                                            std::optional<Fps> renderRate,
+                                            SetFrameRateVote frameRateVote);
     void flushPowerTimeLocked();
     void flushAvailableGlobalRecordsToStatsLocked();
     bool canAddNewAggregatedStats(uid_t uid, const std::string& layerName);
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
index d116b02..a7e7db2 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
+++ b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
@@ -91,6 +91,37 @@
     return result;
 }
 
+std::string TimeStatsHelper::SetFrameRateVote::toString(FrameRateCompatibility compatibility) {
+    switch (compatibility) {
+        case FrameRateCompatibility::Undefined:
+            return "Undefined";
+        case FrameRateCompatibility::Default:
+            return "Default";
+        case FrameRateCompatibility::ExactOrMultiple:
+            return "ExactOrMultiple";
+    }
+}
+
+std::string TimeStatsHelper::SetFrameRateVote::toString(Seamlessness seamlessness) {
+    switch (seamlessness) {
+        case Seamlessness::Undefined:
+            return "Undefined";
+        case Seamlessness::ShouldBeSeamless:
+            return "ShouldBeSeamless";
+        case Seamlessness::NotRequired:
+            return "NotRequired";
+    }
+}
+
+std::string TimeStatsHelper::SetFrameRateVote::toString() const {
+    std::string result;
+    StringAppendF(&result, "frameRate = %.2f\n", frameRate);
+    StringAppendF(&result, "frameRateCompatibility = %s\n",
+                  toString(frameRateCompatibility).c_str());
+    StringAppendF(&result, "seamlessness = %s\n", toString(seamlessness).c_str());
+    return result;
+}
+
 std::string TimeStatsHelper::TimeStatsLayer::toString() const {
     std::string result = "\n";
     StringAppendF(&result, "displayRefreshRate = %d fps\n", displayRefreshRateBucket);
@@ -104,6 +135,8 @@
     StringAppendF(&result, "badDesiredPresentFrames = %d\n", badDesiredPresentFrames);
     result.append("Jank payload for this layer:\n");
     result.append(jankPayload.toString());
+    result.append("SetFrateRate vote for this layer:\n");
+    result.append(setFrameRateVote.toString());
     const auto iter = deltas.find("present2present");
     if (iter != deltas.end()) {
         const float averageTime = iter->second.averageTime();
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
index 5ee28ce..2b37ffe 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
+++ b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
@@ -55,6 +55,28 @@
         std::string toString() const;
     };
 
+    struct SetFrameRateVote {
+        float frameRate = 0;
+
+        // Needs to be in sync with atoms.proto
+        enum class FrameRateCompatibility {
+            Undefined = 0,
+            Default = 1,
+            ExactOrMultiple = 2,
+        } frameRateCompatibility = FrameRateCompatibility::Undefined;
+
+        // Needs to be in sync with atoms.proto
+        enum class Seamlessness {
+            Undefined = 0,
+            ShouldBeSeamless = 1,
+            NotRequired = 2,
+        } seamlessness = Seamlessness::Undefined;
+
+        static std::string toString(FrameRateCompatibility);
+        static std::string toString(Seamlessness);
+        std::string toString() const;
+    };
+
     class TimeStatsLayer {
     public:
         uid_t uid;
@@ -67,6 +89,7 @@
         int32_t lateAcquireFrames = 0;
         int32_t badDesiredPresentFrames = 0;
         JankPayload jankPayload;
+        SetFrameRateVote setFrameRateVote;
         std::unordered_map<std::string, Histogram> deltas;
 
         std::string toString() const;
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index d1385c0..982252f 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -668,9 +668,10 @@
     Fps renderRate = Fps::fromPeriodNsecs(30);
 
     EXPECT_CALL(*mTimeStats,
-                incrementJankyFrames(TimeStats::JankyFramesInfo{refreshRate, renderRate, sUidOne,
-                                                                sLayerNameOne, JankType::Unknown,
-                                                                -1, -1, 25}));
+                incrementJankyFrames(
+                        TimeStats::JankyFramesInfo{refreshRate, renderRate, sUidOne, sLayerNameOne,
+                                                   JankType::Unknown | JankType::AppDeadlineMissed,
+                                                   -1, -1, 25}));
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
     int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60});
     int64_t sfToken1 = mTokenManager->generateTokenForPredictions({82, 90, 90});
@@ -697,7 +698,7 @@
     EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::UnknownPresent);
 
     EXPECT_EQ(surfaceFrame1->getActuals().presentTime, 90);
-    EXPECT_EQ(surfaceFrame1->getJankType(), JankType::Unknown);
+    EXPECT_EQ(surfaceFrame1->getJankType(), JankType::Unknown | JankType::AppDeadlineMissed);
 }
 
 /*
@@ -1699,9 +1700,9 @@
 }
 
 TEST_F(FrameTimelineTest, jankClassification_surfaceFrameLateFinishLatePresent) {
-    // First frame - DisplayFrame is not janky. This should classify the SurfaceFrame as
+    // First frame - DisplayFrame is not janky. This should classify the SurfaceFrame as only
     // AppDeadlineMissed. Second frame - DisplayFrame is janky. This should propagate DisplayFrame's
-    // jank to the SurfaceFrame.
+    // jank to the SurfaceFrame along with AppDeadlineMissed.
 
     EXPECT_CALL(*mTimeStats, incrementJankyFrames(_)).Times(2);
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
@@ -1771,7 +1772,8 @@
     EXPECT_EQ(actuals2.presentTime, 60);
     EXPECT_EQ(presentedSurfaceFrame2.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
     EXPECT_EQ(presentedSurfaceFrame2.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
-    EXPECT_EQ(presentedSurfaceFrame2.getJankType(), JankType::SurfaceFlingerCpuDeadlineMissed);
+    EXPECT_EQ(presentedSurfaceFrame2.getJankType(),
+              JankType::SurfaceFlingerCpuDeadlineMissed | JankType::AppDeadlineMissed);
 }
 
 TEST_F(FrameTimelineTest, jankClassification_multiJankBufferStuffingAndAppDeadlineMissed) {
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 15bd0e1..0b70f27 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -1182,11 +1182,13 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 }
 
-TEST_F(RefreshRateConfigsTest, groupSwitching) {
+TEST_F(RefreshRateConfigsTest, groupSwitchingNotAllowed) {
     auto refreshRateConfigs =
             std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
                                                  /*currentConfigId=*/HWC_CONFIG_ID_60);
 
+    // The default policy doesn't allow group switching. Verify that no
+    // group switches are performed.
     auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
     auto& layer = layers[0];
     layer.vote = LayerVoteType::ExplicitDefault;
@@ -1198,64 +1200,203 @@
     ASSERT_EQ(HWC_CONFIG_ID_60,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
                       .getModeId());
+}
 
+TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayer) {
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
     RefreshRateConfigs::Policy policy;
     policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
     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::ExplicitDefault;
+    layer.desiredRefreshRate = Fps(90.0f);
+    layer.seamlessness = Seamlessness::SeamedAndSeamless;
+    layer.name = "90Hz ExplicitDefault";
+    layer.focused = true;
     ASSERT_EQ(HWC_CONFIG_ID_90,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
                       .getModeId());
+}
+
+TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerOnlySeamless) {
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    RefreshRateConfigs::Policy policy;
+    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
 
     // Verify that we won't change the group if seamless switch is required.
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    auto& layer = layers[0];
+    layer.vote = LayerVoteType::ExplicitDefault;
+    layer.desiredRefreshRate = Fps(90.0f);
     layer.seamlessness = Seamlessness::OnlySeamless;
+    layer.name = "90Hz ExplicitDefault";
+    layer.focused = true;
     ASSERT_EQ(HWC_CONFIG_ID_60,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
                       .getModeId());
+}
+
+TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerOnlySeamlessDefaultFps) {
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    RefreshRateConfigs::Policy policy;
+    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+
+    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
 
     // Verify that we won't do a seamless switch if we request the same mode as the default
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    auto& layer = layers[0];
+    layer.vote = LayerVoteType::ExplicitDefault;
     layer.desiredRefreshRate = Fps(60.0f);
-    layer.name = "60Hz ExplicitDefault";
     layer.seamlessness = Seamlessness::OnlySeamless;
+    layer.name = "60Hz ExplicitDefault";
+    layer.focused = true;
     ASSERT_EQ(HWC_CONFIG_ID_90,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
                       .getModeId());
+}
+
+TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerDefaultSeamlessness) {
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    RefreshRateConfigs::Policy policy;
+    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+
+    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
 
     // Verify that if the current config is in another group and there are no layers with
     // seamlessness=SeamedAndSeamless we'll go back to the default group.
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    auto& layer = layers[0];
+    layer.vote = LayerVoteType::ExplicitDefault;
     layer.desiredRefreshRate = Fps(60.0f);
-    layer.name = "60Hz ExplicitDefault";
     layer.seamlessness = Seamlessness::Default;
+    layer.name = "60Hz ExplicitDefault";
+    layer.focused = true;
+
     ASSERT_EQ(HWC_CONFIG_ID_60,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
                       .getModeId());
+}
+
+TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersOnlySeamlessAndSeamed) {
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    RefreshRateConfigs::Policy policy;
+    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+
+    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
 
     // If there's a layer with seamlessness=SeamedAndSeamless, another layer with
     // seamlessness=OnlySeamless can't change the mode group.
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
-    layer.seamlessness = Seamlessness::OnlySeamless;
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+    layers[0].desiredRefreshRate = Fps(60.0f);
+    layers[0].seamlessness = Seamlessness::OnlySeamless;
+    layers[0].name = "60Hz ExplicitDefault";
+    layers[0].focused = true;
 
     layers.push_back(LayerRequirement{.weight = 0.5f});
-    auto& layer2 = layers[layers.size() - 1];
-    layer2.vote = LayerVoteType::ExplicitDefault;
-    layer2.desiredRefreshRate = Fps(90.0f);
-    layer2.name = "90Hz ExplicitDefault";
-    layer2.seamlessness = Seamlessness::SeamedAndSeamless;
-    layer2.focused = false;
+    layers[1].vote = LayerVoteType::ExplicitDefault;
+    layers[1].seamlessness = Seamlessness::SeamedAndSeamless;
+    layers[1].desiredRefreshRate = Fps(90.0f);
+    layers[1].name = "90Hz ExplicitDefault";
+    layers[1].focused = false;
 
     ASSERT_EQ(HWC_CONFIG_ID_90,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
                       .getModeId());
+}
 
-    // If there's a layer with seamlessness=SeamedAndSeamless, another layer with
-    // seamlessness=Default can't change the mode group.
+TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersDefaultFocusedAndSeamed) {
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    RefreshRateConfigs::Policy policy;
+    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+
+    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
+
+    // If there's a focused layer with seamlessness=SeamedAndSeamless, another layer with
+    // seamlessness=Default can't change the mode group back to the group of the default
+    // mode.
+    // For example, this may happen when a video playback requests and gets a seamed switch,
+    // but another layer (with default seamlessness) starts animating. The animating layer
+    // should not cause a seamed switch.
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
     layers[0].seamlessness = Seamlessness::Default;
+    layers[0].desiredRefreshRate = Fps(60.0f);
+    layers[0].focused = true;
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+    layers[0].name = "60Hz ExplicitDefault";
+
+    layers.push_back(LayerRequirement{.weight = 0.1f});
+    layers[1].seamlessness = Seamlessness::SeamedAndSeamless;
+    layers[1].desiredRefreshRate = Fps(90.0f);
+    layers[1].focused = true;
+    layers[1].vote = LayerVoteType::ExplicitDefault;
+    layers[1].name = "90Hz ExplicitDefault";
+
     ASSERT_EQ(HWC_CONFIG_ID_90,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
                       .getModeId());
 }
 
+TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersDefaultNotFocusedAndSeamed) {
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
+                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    RefreshRateConfigs::Policy policy;
+    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+
+    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
+
+    // Layer with seamlessness=Default can change the mode group if there's a not
+    // focused layer with seamlessness=SeamedAndSeamless. This happens for example,
+    // when in split screen mode the user switches between the two visible applications.
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    layers[0].seamlessness = Seamlessness::Default;
+    layers[0].desiredRefreshRate = Fps(60.0f);
+    layers[0].focused = true;
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+    layers[0].name = "60Hz ExplicitDefault";
+
+    layers.push_back(LayerRequirement{.weight = 0.7f});
+    layers[1].seamlessness = Seamlessness::SeamedAndSeamless;
+    layers[1].desiredRefreshRate = Fps(90.0f);
+    layers[1].focused = false;
+    layers[1].vote = LayerVoteType::ExplicitDefault;
+    layers[1].name = "90Hz ExplicitDefault";
+
+    ASSERT_EQ(HWC_CONFIG_ID_60,
+              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
+                      .getModeId());
+}
+
 TEST_F(RefreshRateConfigsTest, nonSeamlessVotePrefersSeamlessSwitches) {
     auto refreshRateConfigs =
             std::make_unique<RefreshRateConfigs>(m30_60Device,
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
index 5c8c2d8..7ef1f2b 100644
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
@@ -475,18 +475,36 @@
                          PrintToStringParamName);
 
 TEST_F(SetFrameRateTest, ValidateFrameRate) {
-    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, ""));
-    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, ""));
-    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, ""));
-    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, "", /*privileged=*/true));
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS, ""));
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "",
+                                  /*privileged=*/true));
 
-    EXPECT_FALSE(ValidateFrameRate(-1, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, ""));
-    EXPECT_FALSE(
-            ValidateFrameRate(1.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, ""));
-    EXPECT_FALSE(
-            ValidateFrameRate(0.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, ""));
+    EXPECT_FALSE(ValidateFrameRate(-1, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_FALSE(ValidateFrameRate(1.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_FALSE(ValidateFrameRate(0.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
 
-    EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, ""));
+    EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
+                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+    // Invalid compatibility
+    EXPECT_FALSE(
+            ValidateFrameRate(60.0f, -1, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_FALSE(ValidateFrameRate(60.0f, 2, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+    // Invalid change frame rate strategy
+    EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, -1, ""));
+    EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, 2, ""));
 }
 
 TEST_P(SetFrameRateTest, SetOnParentActivatesTree) {
diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
index a72ab42..c1f0c4e 100644
--- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
@@ -142,15 +142,16 @@
 
     std::string inputCommand(InputCommand cmd, bool useProto);
 
-    void setTimeStamp(TimeStamp type, int32_t id, uint64_t frameNumber, nsecs_t ts);
+    void setTimeStamp(TimeStamp type, int32_t id, uint64_t frameNumber, nsecs_t ts,
+                      TimeStats::SetFrameRateVote frameRateVote);
 
     int32_t genRandomInt32(int32_t begin, int32_t end);
 
     template <size_t N>
     void insertTimeRecord(const TimeStamp (&sequence)[N], int32_t id, uint64_t frameNumber,
-                          nsecs_t ts) {
+                          nsecs_t ts, TimeStats::SetFrameRateVote frameRateVote = {}) {
         for (size_t i = 0; i < N; i++, ts += 1000000) {
-            setTimeStamp(sequence[i], id, frameNumber, ts);
+            setTimeStamp(sequence[i], id, frameNumber, ts, frameRateVote);
         }
     }
 
@@ -234,7 +235,8 @@
     return (layerId < 0 ? "PopupWindow:b54fcd1#0" : "com.example.fake#") + std::to_string(layerId);
 }
 
-void TimeStatsTest::setTimeStamp(TimeStamp type, int32_t id, uint64_t frameNumber, nsecs_t ts) {
+void TimeStatsTest::setTimeStamp(TimeStamp type, int32_t id, uint64_t frameNumber, nsecs_t ts,
+                                 TimeStats::SetFrameRateVote frameRateVote) {
     switch (type) {
         case TimeStamp::POST:
             ASSERT_NO_FATAL_FAILURE(
@@ -254,13 +256,13 @@
             ASSERT_NO_FATAL_FAILURE(mTimeStats->setDesiredTime(id, frameNumber, ts));
             break;
         case TimeStamp::PRESENT:
-            ASSERT_NO_FATAL_FAILURE(
-                    mTimeStats->setPresentTime(id, frameNumber, ts, kRefreshRate0, kRenderRate0));
+            ASSERT_NO_FATAL_FAILURE(mTimeStats->setPresentTime(id, frameNumber, ts, kRefreshRate0,
+                                                               kRenderRate0, frameRateVote));
             break;
         case TimeStamp::PRESENT_FENCE:
-            ASSERT_NO_FATAL_FAILURE(mTimeStats->setPresentFence(id, frameNumber,
-                                                                std::make_shared<FenceTime>(ts),
-                                                                kRefreshRate0, kRenderRate0));
+            ASSERT_NO_FATAL_FAILURE(
+                    mTimeStats->setPresentFence(id, frameNumber, std::make_shared<FenceTime>(ts),
+                                                kRefreshRate0, kRenderRate0, frameRateVote));
             break;
         default:
             ALOGD("Invalid timestamp type");
@@ -411,6 +413,96 @@
     EXPECT_THAT(result, HasSubstr(expectedResult));
 }
 
+TEST_F(TimeStatsTest, canCaptureSetFrameRateVote) {
+    // this stat is not in the proto so verify by checking the string dump
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
+
+    const auto frameRate60 = TimeStats::SetFrameRateVote{
+            .frameRate = 60.0f,
+            .frameRateCompatibility = TimeStats::SetFrameRateVote::FrameRateCompatibility::Default,
+            .seamlessness = TimeStats::SetFrameRateVote::Seamlessness::ShouldBeSeamless,
+    };
+    const auto frameRate90 = TimeStats::SetFrameRateVote{
+            .frameRate = 90.0f,
+            .frameRateCompatibility =
+                    TimeStats::SetFrameRateVote::FrameRateCompatibility::ExactOrMultiple,
+            .seamlessness = TimeStats::SetFrameRateVote::Seamlessness::NotRequired,
+    };
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000, frameRate60);
+    std::string result(inputCommand(InputCommand::DUMP_ALL, FMT_STRING));
+    std::string expectedResult = "frameRate = 60.00";
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+    expectedResult = "frameRateCompatibility = Default";
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+    expectedResult = "seamlessness = ShouldBeSeamless";
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 3, 3000000, frameRate90);
+    result = inputCommand(InputCommand::DUMP_ALL, FMT_STRING);
+    expectedResult = "frameRate = 90.00";
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+    expectedResult = "frameRateCompatibility = ExactOrMultiple";
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+    expectedResult = "seamlessness = NotRequired";
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+
+    insertTimeRecord(NORMAL_SEQUENCE_2, LAYER_ID_0, 4, 4000000, frameRate60);
+    result = inputCommand(InputCommand::DUMP_ALL, FMT_STRING);
+    expectedResult = "frameRate = 60.00";
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+    expectedResult = "frameRateCompatibility = Default";
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+    expectedResult = "seamlessness = ShouldBeSeamless";
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+}
+
+TEST_F(TimeStatsTest, canCaptureSetFrameRateVoteAfterZeroForLayer) {
+    // this stat is not in the proto so verify by checking the string dump
+    EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
+
+    const auto frameRate90 = TimeStats::SetFrameRateVote{
+            .frameRate = 90.0f,
+            .frameRateCompatibility =
+                    TimeStats::SetFrameRateVote::FrameRateCompatibility::ExactOrMultiple,
+            .seamlessness = TimeStats::SetFrameRateVote::Seamlessness::NotRequired,
+    };
+    const auto frameRateDefault = TimeStats::SetFrameRateVote{
+            .frameRate = 0.0f,
+            .frameRateCompatibility = TimeStats::SetFrameRateVote::FrameRateCompatibility::Default,
+            .seamlessness = TimeStats::SetFrameRateVote::Seamlessness::ShouldBeSeamless,
+    };
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000, frameRate90);
+    std::string result(inputCommand(InputCommand::DUMP_ALL, FMT_STRING));
+    std::string expectedResult = "frameRate = 90.00";
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+    expectedResult = "frameRateCompatibility = ExactOrMultiple";
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+    expectedResult = "seamlessness = NotRequired";
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 3, 3000000, frameRateDefault);
+    result = inputCommand(InputCommand::DUMP_ALL, FMT_STRING);
+    expectedResult = "frameRate = 90.00";
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+    expectedResult = "frameRateCompatibility = ExactOrMultiple";
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+    expectedResult = "seamlessness = NotRequired";
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+
+    insertTimeRecord(NORMAL_SEQUENCE_2, LAYER_ID_0, 4, 4000000, frameRateDefault);
+    result = inputCommand(InputCommand::DUMP_ALL, FMT_STRING);
+    expectedResult = "frameRate = 90.00";
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+    expectedResult = "frameRateCompatibility = ExactOrMultiple";
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+    expectedResult = "seamlessness = NotRequired";
+    EXPECT_THAT(result, HasSubstr(expectedResult));
+}
+
 TEST_F(TimeStatsTest, canIncreaseClientCompositionReusedFrames) {
     // this stat is not in the proto so verify by checking the string dump
     constexpr size_t CLIENT_COMPOSITION_REUSED_FRAMES = 2;
@@ -936,12 +1028,15 @@
     return byteString;
 }
 
-std::string frameRateVoteToProtoByteString(float refreshRate, int frameRateCompatibility,
-                                           int seamlessness) {
+std::string frameRateVoteToProtoByteString(
+        float refreshRate,
+        TimeStats::SetFrameRateVote::FrameRateCompatibility frameRateCompatibility,
+        TimeStats::SetFrameRateVote::Seamlessness seamlessness) {
     util::ProtoOutputStream proto;
     proto.write(android::util::FIELD_TYPE_FLOAT | 1 /* field id */, refreshRate);
-    proto.write(android::util::FIELD_TYPE_ENUM | 2 /* field id */, frameRateCompatibility);
-    proto.write(android::util::FIELD_TYPE_ENUM | 3 /* field id */, seamlessness);
+    proto.write(android::util::FIELD_TYPE_ENUM | 2 /* field id */,
+                static_cast<int>(frameRateCompatibility));
+    proto.write(android::util::FIELD_TYPE_ENUM | 3 /* field id */, static_cast<int>(seamlessness));
 
     std::string byteString;
     proto.serializeToString(&byteString);
@@ -1149,7 +1244,13 @@
     for (size_t i = 0; i < BAD_DESIRED_PRESENT_FRAMES; i++) {
         mTimeStats->incrementBadDesiredPresent(LAYER_ID_0);
     }
-    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000);
+    const auto frameRate60 = TimeStats::SetFrameRateVote{
+            .frameRate = 60.0f,
+            .frameRateCompatibility =
+                    TimeStats::SetFrameRateVote::FrameRateCompatibility::ExactOrMultiple,
+            .seamlessness = TimeStats::SetFrameRateVote::Seamlessness::NotRequired,
+    };
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000, frameRate60);
 
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
                                       JankType::SurfaceFlingerCpuDeadlineMissed, 1, 2, 3});
@@ -1181,7 +1282,10 @@
     std::string expectedLatchToPresent = buildExpectedHistogramBytestring({2}, {1});
     std::string expectedDesiredToPresent = buildExpectedHistogramBytestring({1}, {1});
     std::string expectedPostToAcquire = buildExpectedHistogramBytestring({1}, {1});
-    std::string expectedFrameRateOverride = frameRateVoteToProtoByteString(0.0, 0, 0);
+    std::string expectedFrameRateOverride =
+            frameRateVoteToProtoByteString(frameRate60.frameRate,
+                                           frameRate60.frameRateCompatibility,
+                                           frameRate60.seamlessness);
     std::string expectedAppDeadlineMissed = buildExpectedHistogramBytestring({3, 2}, {4, 3});
     {
         InSequence seq;
@@ -1455,7 +1559,7 @@
         TimeStamp type = static_cast<TimeStamp>(genRandomInt32(TIME_STAMP_BEGIN, TIME_STAMP_END));
         const int32_t ts = genRandomInt32(1, 1000000000);
         ALOGV("type[%d], layerId[%d], frameNumber[%d], ts[%d]", type, layerId, frameNumber, ts);
-        setTimeStamp(type, layerId, frameNumber, ts);
+        setTimeStamp(type, layerId, frameNumber, ts, {});
     }
 }
 
diff --git a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
index 3e4a0b8..37b74ed 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
@@ -48,10 +48,11 @@
     MOCK_METHOD3(setDesiredTime, void(int32_t, uint64_t, nsecs_t));
     MOCK_METHOD3(setAcquireTime, void(int32_t, uint64_t, nsecs_t));
     MOCK_METHOD3(setAcquireFence, void(int32_t, uint64_t, const std::shared_ptr<FenceTime>&));
-    MOCK_METHOD5(setPresentTime, void(int32_t, uint64_t, nsecs_t, Fps, std::optional<Fps>));
-    MOCK_METHOD5(setPresentFence,
-                 void(int32_t, uint64_t, const std::shared_ptr<FenceTime>&, Fps,
-                      std::optional<Fps>));
+    MOCK_METHOD6(setPresentTime,
+                 void(int32_t, uint64_t, nsecs_t, Fps, std::optional<Fps>, SetFrameRateVote));
+    MOCK_METHOD6(setPresentFence,
+                 void(int32_t, uint64_t, const std::shared_ptr<FenceTime>&, Fps, std::optional<Fps>,
+                      SetFrameRateVote));
     MOCK_METHOD1(incrementJankyFrames, void(const JankyFramesInfo&));
     MOCK_METHOD1(onDestroy, void(int32_t));
     MOCK_METHOD2(removeTimeRecord, void(int32_t, uint64_t));