Merge changes I15e0f5a2,I0db8970b

* changes:
  SF: Obey active display's RefreshRateSelector
  SF: Do not deref empty std::optional<PowerMode>
diff --git a/cmds/servicemanager/Android.bp b/cmds/servicemanager/Android.bp
index 61f931e..1386660 100644
--- a/cmds/servicemanager/Android.bp
+++ b/cmds/servicemanager/Android.bp
@@ -48,17 +48,6 @@
 }
 
 cc_binary {
-    name: "servicemanager.microdroid",
-    defaults: ["servicemanager_defaults"],
-    init_rc: ["servicemanager.microdroid.rc"],
-    srcs: ["main.cpp"],
-    bootstrap: true,
-    // Prevent this from being installed when running tests in this directory.
-    // This is okay because microdorid build rule can bundle this anyway.
-    installable: false,
-}
-
-cc_binary {
     name: "servicemanager.recovery",
     stem: "servicemanager",
     recovery: true,
diff --git a/cmds/servicemanager/servicemanager.microdroid.rc b/cmds/servicemanager/servicemanager.microdroid.rc
deleted file mode 100644
index 8819e1e..0000000
--- a/cmds/servicemanager/servicemanager.microdroid.rc
+++ /dev/null
@@ -1,8 +0,0 @@
-service servicemanager /system/bin/servicemanager.microdroid
-    class core
-    user system
-    group system readproc
-    critical
-    onrestart setprop servicemanager.ready false
-    onrestart restart apexd
-    shutdown critical
diff --git a/libs/gui/aidl/android/gui/SupportedBufferCombinations.aidl b/include/ftl/details/mixins.h
similarity index 62%
rename from libs/gui/aidl/android/gui/SupportedBufferCombinations.aidl
rename to include/ftl/details/mixins.h
index a8bc994..9ab9e08 100644
--- a/libs/gui/aidl/android/gui/SupportedBufferCombinations.aidl
+++ b/include/ftl/details/mixins.h
@@ -14,10 +14,17 @@
  * limitations under the License.
  */
 
-package android.gui;
+#pragma once
 
-/** @hide */
-parcelable SupportedBufferCombinations {
-    int[] pixelFormats;
-    int[] dataspaces;
-}
+namespace android::ftl::details {
+
+template <typename Self, template <typename> class>
+class Mixin {
+ protected:
+  constexpr Self& self() { return *static_cast<Self*>(this); }
+  constexpr const Self& self() const { return *static_cast<const Self*>(this); }
+
+  constexpr auto& mut() { return self().value_; }
+};
+
+}  // namespace android::ftl::details
diff --git a/include/ftl/enum.h b/include/ftl/enum.h
index 82af1d6..075d12b 100644
--- a/include/ftl/enum.h
+++ b/include/ftl/enum.h
@@ -92,7 +92,7 @@
 //   enum class E { A, B, C };
 //   static_assert(ftl::to_underlying(E::B) == 1);
 //
-template <typename E>
+template <typename E, typename = std::enable_if_t<std::is_enum_v<E>>>
 constexpr auto to_underlying(E v) {
   return static_cast<std::underlying_type_t<E>>(v);
 }
diff --git a/include/ftl/mixins.h b/include/ftl/mixins.h
new file mode 100644
index 0000000..0e1d200
--- /dev/null
+++ b/include/ftl/mixins.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <ftl/details/mixins.h>
+
+namespace android::ftl {
+
+// CRTP mixins for defining type-safe wrappers that are distinct from their underlying type. Common
+// uses are IDs, opaque handles, and physical quantities. The constructor is provided by (and must
+// be inherited from) the `Constructible` mixin, whereas operators (equality, ordering, arithmetic,
+// etc.) are enabled through inheritance:
+//
+//   struct Id : ftl::Constructible<Id, std::int32_t>, ftl::Equatable<Id> {
+//     using Constructible::Constructible;
+//   };
+//
+//   static_assert(!std::is_default_constructible_v<Id>);
+//
+// Unlike `Constructible`, `DefaultConstructible` allows default construction. The default value is
+// zero-initialized unless specified:
+//
+//   struct Color : ftl::DefaultConstructible<Color, std::uint8_t>,
+//                  ftl::Equatable<Color>,
+//                  ftl::Orderable<Color> {
+//     using DefaultConstructible::DefaultConstructible;
+//   };
+//
+//   static_assert(Color() == Color(0u));
+//   static_assert(ftl::to_underlying(Color(-1)) == 255u);
+//   static_assert(Color(1u) < Color(2u));
+//
+//   struct Sequence : ftl::DefaultConstructible<Sequence, std::int8_t, -1>,
+//                     ftl::Equatable<Sequence>,
+//                     ftl::Orderable<Sequence>,
+//                     ftl::Incrementable<Sequence> {
+//     using DefaultConstructible::DefaultConstructible;
+//   };
+//
+//   static_assert(Sequence() == Sequence(-1));
+//
+// The underlying type need not be a fundamental type:
+//
+//   struct Timeout : ftl::DefaultConstructible<Timeout, std::chrono::seconds, 10>,
+//                    ftl::Equatable<Timeout>,
+//                    ftl::Addable<Timeout> {
+//     using DefaultConstructible::DefaultConstructible;
+//   };
+//
+//   using namespace std::chrono_literals;
+//   static_assert(Timeout() + Timeout(5s) == Timeout(15s));
+//
+template <typename Self, typename T>
+struct Constructible {
+  explicit constexpr Constructible(T value) : value_(value) {}
+
+  explicit constexpr operator const T&() const { return value_; }
+
+ private:
+  template <typename, template <typename> class>
+  friend class details::Mixin;
+
+  T value_;
+};
+
+template <typename Self, typename T, auto kDefault = T{}>
+struct DefaultConstructible : Constructible<Self, T> {
+  using Constructible<Self, T>::Constructible;
+  constexpr DefaultConstructible() : DefaultConstructible(T{kDefault}) {}
+};
+
+// Shorthand for casting a type-safe wrapper to its underlying value.
+template <typename Self, typename T>
+constexpr const T& to_underlying(const Constructible<Self, T>& c) {
+  return static_cast<const T&>(c);
+}
+
+// Comparison operators for equality.
+template <typename Self>
+struct Equatable : details::Mixin<Self, Equatable> {
+  constexpr bool operator==(const Self& other) const {
+    return to_underlying(this->self()) == to_underlying(other);
+  }
+
+  constexpr bool operator!=(const Self& other) const { return !(*this == other); }
+};
+
+// Comparison operators for ordering.
+template <typename Self>
+struct Orderable : details::Mixin<Self, Orderable> {
+  constexpr bool operator<(const Self& other) const {
+    return to_underlying(this->self()) < to_underlying(other);
+  }
+
+  constexpr bool operator>(const Self& other) const { return other < this->self(); }
+  constexpr bool operator>=(const Self& other) const { return !(*this < other); }
+  constexpr bool operator<=(const Self& other) const { return !(*this > other); }
+};
+
+// Pre-increment and post-increment operators.
+template <typename Self>
+struct Incrementable : details::Mixin<Self, Incrementable> {
+  constexpr Self& operator++() {
+    ++this->mut();
+    return this->self();
+  }
+
+  constexpr Self operator++(int) {
+    const Self tmp = this->self();
+    operator++();
+    return tmp;
+  }
+};
+
+// Additive operators, including incrementing.
+template <typename Self>
+struct Addable : details::Mixin<Self, Addable>, Incrementable<Self> {
+  constexpr Self& operator+=(const Self& other) {
+    this->mut() += to_underlying(other);
+    return this->self();
+  }
+
+  constexpr Self operator+(const Self& other) const {
+    Self tmp = this->self();
+    return tmp += other;
+  }
+
+ private:
+  using Base = details::Mixin<Self, Addable>;
+  using Base::mut;
+  using Base::self;
+};
+
+}  // namespace android::ftl
diff --git a/include/ftl/optional.h b/include/ftl/optional.h
index 626507f..7b02bac 100644
--- a/include/ftl/optional.h
+++ b/include/ftl/optional.h
@@ -97,6 +97,16 @@
   }
 };
 
+template <typename T, typename U>
+constexpr bool operator==(const Optional<T>& lhs, const Optional<U>& rhs) {
+  return static_cast<std::optional<T>>(lhs) == static_cast<std::optional<U>>(rhs);
+}
+
+template <typename T, typename U>
+constexpr bool operator!=(const Optional<T>& lhs, const Optional<U>& rhs) {
+  return !(lhs == rhs);
+}
+
 // Deduction guides.
 template <typename T>
 Optional(T) -> Optional<T>;
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 11c8e5d..7770374 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -45,11 +45,11 @@
 
 #define IF_LOG_TRANSACTIONS() if (false)
 #define IF_LOG_COMMANDS() if (false)
-#define LOG_REMOTEREFS(...) 
+#define LOG_REMOTEREFS(...)
 #define IF_LOG_REMOTEREFS() if (false)
 
-#define LOG_THREADPOOL(...) 
-#define LOG_ONEWAY(...) 
+#define LOG_THREADPOOL(...)
+#define LOG_ONEWAY(...)
 
 #else
 
@@ -394,14 +394,92 @@
     // context, so we don't abort
 }
 
+constexpr uint32_t encodeExplicitIdentity(bool hasExplicitIdentity, pid_t callingPid) {
+    uint32_t as_unsigned = static_cast<uint32_t>(callingPid);
+    if (hasExplicitIdentity) {
+        return as_unsigned | (1 << 30);
+    } else {
+        return as_unsigned & ~(1 << 30);
+    }
+}
+
+constexpr int64_t packCallingIdentity(bool hasExplicitIdentity, uid_t callingUid,
+                                      pid_t callingPid) {
+    // Calling PID is a 32-bit signed integer, but doesn't consume the entire 32 bit space.
+    // To future-proof this and because we have extra capacity, we decided to also support -1,
+    // since this constant is used to represent invalid UID in other places of the system.
+    // Thus, we pack hasExplicitIdentity into the 2nd bit from the left.  This allows us to
+    // preserve the (left-most) bit for the sign while also encoding the value of
+    // hasExplicitIdentity.
+    //               32b     |        1b         |         1b            |        30b
+    // token = [ calling uid | calling pid(sign) | has explicit identity | calling pid(rest) ]
+    uint64_t token = (static_cast<uint64_t>(callingUid) << 32) |
+            encodeExplicitIdentity(hasExplicitIdentity, callingPid);
+    return static_cast<int64_t>(token);
+}
+
+constexpr bool unpackHasExplicitIdentity(int64_t token) {
+    return static_cast<int32_t>(token) & (1 << 30);
+}
+
+constexpr uid_t unpackCallingUid(int64_t token) {
+    return static_cast<uid_t>(token >> 32);
+}
+
+constexpr pid_t unpackCallingPid(int64_t token) {
+    int32_t encodedPid = static_cast<int32_t>(token);
+    if (encodedPid & (1 << 31)) {
+        return encodedPid | (1 << 30);
+    } else {
+        return encodedPid & ~(1 << 30);
+    }
+}
+
+static_assert(unpackHasExplicitIdentity(packCallingIdentity(true, 1000, 9999)) == true,
+              "pack true hasExplicit");
+
+static_assert(unpackCallingUid(packCallingIdentity(true, 1000, 9999)) == 1000, "pack true uid");
+
+static_assert(unpackCallingPid(packCallingIdentity(true, 1000, 9999)) == 9999, "pack true pid");
+
+static_assert(unpackHasExplicitIdentity(packCallingIdentity(false, 1000, 9999)) == false,
+              "pack false hasExplicit");
+
+static_assert(unpackCallingUid(packCallingIdentity(false, 1000, 9999)) == 1000, "pack false uid");
+
+static_assert(unpackCallingPid(packCallingIdentity(false, 1000, 9999)) == 9999, "pack false pid");
+
+static_assert(unpackHasExplicitIdentity(packCallingIdentity(true, 1000, -1)) == true,
+              "pack true (negative) hasExplicit");
+
+static_assert(unpackCallingUid(packCallingIdentity(true, 1000, -1)) == 1000,
+              "pack true (negative) uid");
+
+static_assert(unpackCallingPid(packCallingIdentity(true, 1000, -1)) == -1,
+              "pack true (negative) pid");
+
+static_assert(unpackHasExplicitIdentity(packCallingIdentity(false, 1000, -1)) == false,
+              "pack false (negative) hasExplicit");
+
+static_assert(unpackCallingUid(packCallingIdentity(false, 1000, -1)) == 1000,
+              "pack false (negative) uid");
+
+static_assert(unpackCallingPid(packCallingIdentity(false, 1000, -1)) == -1,
+              "pack false (negative) pid");
+
 int64_t IPCThreadState::clearCallingIdentity()
 {
     // ignore mCallingSid for legacy reasons
-    int64_t token = ((int64_t)mCallingUid<<32) | mCallingPid;
+    int64_t token = packCallingIdentity(mHasExplicitIdentity, mCallingUid, mCallingPid);
     clearCaller();
+    mHasExplicitIdentity = true;
     return token;
 }
 
+bool IPCThreadState::hasExplicitIdentity() {
+    return mHasExplicitIdentity;
+}
+
 void IPCThreadState::setStrictModePolicy(int32_t policy)
 {
     mStrictModePolicy = policy;
@@ -474,9 +552,10 @@
 
 void IPCThreadState::restoreCallingIdentity(int64_t token)
 {
-    mCallingUid = (int)(token>>32);
+    mCallingUid = unpackCallingUid(token);
     mCallingSid = nullptr;  // not enough data to restore
-    mCallingPid = (int)token;
+    mCallingPid = unpackCallingPid(token);
+    mHasExplicitIdentity = unpackHasExplicitIdentity(token);
 }
 
 void IPCThreadState::clearCaller()
@@ -889,6 +968,7 @@
         mCallRestriction(mProcess->mCallRestriction) {
     pthread_setspecific(gTLS, this);
     clearCaller();
+    mHasExplicitIdentity = false;
     mIn.setDataCapacity(256);
     mOut.setDataCapacity(256);
 }
@@ -1279,6 +1359,7 @@
             const pid_t origPid = mCallingPid;
             const char* origSid = mCallingSid;
             const uid_t origUid = mCallingUid;
+            const bool origHasExplicitIdentity = mHasExplicitIdentity;
             const int32_t origStrictModePolicy = mStrictModePolicy;
             const int32_t origTransactionBinderFlags = mLastTransactionBinderFlags;
             const int32_t origWorkSource = mWorkSource;
@@ -1292,6 +1373,7 @@
             mCallingPid = tr.sender_pid;
             mCallingSid = reinterpret_cast<const char*>(tr_secctx.secctx);
             mCallingUid = tr.sender_euid;
+            mHasExplicitIdentity = false;
             mLastTransactionBinderFlags = tr.flags;
 
             // ALOGI(">>>> TRANSACT from pid %d sid %s uid %d\n", mCallingPid,
@@ -1367,6 +1449,7 @@
             mCallingPid = origPid;
             mCallingSid = origSid;
             mCallingUid = origUid;
+            mHasExplicitIdentity = origHasExplicitIdentity;
             mStrictModePolicy = origStrictModePolicy;
             mLastTransactionBinderFlags = origTransactionBinderFlags;
             mWorkSource = origWorkSource;
diff --git a/libs/binder/include/binder/IPCThreadState.h b/libs/binder/include/binder/IPCThreadState.h
index c01e92f..65b77c6 100644
--- a/libs/binder/include/binder/IPCThreadState.h
+++ b/libs/binder/include/binder/IPCThreadState.h
@@ -139,6 +139,7 @@
             int64_t             clearCallingIdentity();
             // Restores PID/UID (not SID)
             void                restoreCallingIdentity(int64_t token);
+            bool hasExplicitIdentity();
 
             status_t            setupPolling(int* fd);
             status_t            handlePolledCommands();
@@ -241,6 +242,7 @@
             bool                mPropagateWorkSource;
             bool                mIsLooper;
             bool mIsFlushing;
+            bool mHasExplicitIdentity;
             int32_t             mStrictModePolicy;
             int32_t             mLastTransactionBinderFlags;
             CallRestriction     mCallRestriction;
diff --git a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
index dd177af..f08bde8 100644
--- a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
+++ b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
@@ -17,20 +17,41 @@
 #pragma once
 
 #include <sys/socket.h>
+#include <stdint.h>
 
 extern "C" {
 
 struct AIBinder;
+struct ARpcServer;
 
 // Starts an RPC server on a given port and a given root IBinder object.
-// This function sets up the server and joins before returning.
-bool RunVsockRpcServer(AIBinder* service, unsigned int port);
+// Returns an opaque handle to the running server instance, or null if the server
+// could not be started.
+[[nodiscard]] ARpcServer* ARpcServer_newVsock(AIBinder* service, unsigned int port);
 
-// Starts an RPC server on a given port and a given root IBinder object.
-// This function sets up the server, calls readyCallback with a given param, and
-// then joins before returning.
-bool RunVsockRpcServerCallback(AIBinder* service, unsigned int port,
-                               void (*readyCallback)(void* param), void* param);
+// Starts a Unix domain RPC server with a given init-managed Unix domain `name`
+// and a given root IBinder object.
+// The socket should be created in init.rc with the same `name`.
+// Returns an opaque handle to the running server instance, or null if the server
+// could not be started.
+[[nodiscard]] ARpcServer* ARpcServer_newInitUnixDomain(AIBinder* service, const char* name);
+
+// Runs ARpcServer_join() in a background thread. Immediately returns.
+void ARpcServer_start(ARpcServer* server);
+
+// Joins the thread of a running RpcServer instance. At any given point, there
+// can only be one thread calling ARpcServer_join().
+// If a client needs to actively terminate join, call ARpcServer_shutdown() in
+// a separate thread.
+void ARpcServer_join(ARpcServer* server);
+
+// Shuts down any running ARpcServer_join().
+void ARpcServer_shutdown(ARpcServer* server);
+
+// Frees the ARpcServer handle and drops the reference count on the underlying
+// RpcServer instance. The handle must not be reused afterwards.
+// This automatically calls ARpcServer_shutdown().
+void ARpcServer_free(ARpcServer* server);
 
 // Starts an RPC server on a given port and a given root IBinder factory.
 // RunVsockRpcServerWithFactory acts like RunVsockRpcServerCallback, but instead of
@@ -42,15 +63,6 @@
 
 AIBinder* VsockRpcClient(unsigned int cid, unsigned int port);
 
-// Starts a Unix domain RPC server with a given init-managed Unix domain `name` and
-// a given root IBinder object.
-// The socket should be created in init.rc with the same `name`.
-//
-// This function sets up the server, calls readyCallback with a given param, and
-// then joins before returning.
-bool RunInitUnixDomainRpcServer(AIBinder* service, const char* name,
-                                void (*readyCallback)(void* param), void* param);
-
 // Gets the service via the RPC binder with Unix domain socket with the given
 // Unix socket `name`.
 // The final Unix domain socket path name is /dev/socket/`name`.
diff --git a/libs/binder/libbinder_rpc_unstable.cpp b/libs/binder/libbinder_rpc_unstable.cpp
index 9edb3b6..f55c779 100644
--- a/libs/binder/libbinder_rpc_unstable.cpp
+++ b/libs/binder/libbinder_rpc_unstable.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <binder_rpc_unstable.hpp>
+
 #include <android-base/logging.h>
 #include <android-base/unique_fd.h>
 #include <android/binder_libbinder.h>
@@ -25,23 +27,32 @@
 using android::OK;
 using android::RpcServer;
 using android::RpcSession;
+using android::sp;
 using android::status_t;
 using android::statusToString;
 using android::base::unique_fd;
 
-extern "C" {
+// Opaque handle for RpcServer.
+struct ARpcServer {};
 
-void RunRpcServer(android::sp<RpcServer>& server, AIBinder* service,
-                  void (*readyCallback)(void* param), void* param) {
-    server->setRootObject(AIBinder_toPlatformBinder(service));
-
-    if (readyCallback) readyCallback(param);
-    server->join();
-
-    // Shutdown any open sessions since server failed.
-    (void)server->shutdown();
+static sp<RpcServer> toRpcServer(ARpcServer* handle) {
+    auto ref = reinterpret_cast<RpcServer*>(handle);
+    return sp<RpcServer>::fromExisting(ref);
 }
 
+static ARpcServer* createRpcServerHandle(sp<RpcServer>& server) {
+    auto ref = server.get();
+    ref->incStrong(ref);
+    return reinterpret_cast<ARpcServer*>(ref);
+}
+
+static void freeRpcServerHandle(ARpcServer* handle) {
+    auto ref = reinterpret_cast<RpcServer*>(handle);
+    ref->decStrong(ref);
+}
+
+extern "C" {
+
 bool RunVsockRpcServerWithFactory(AIBinder* (*factory)(unsigned int cid, void* context),
                                   void* factoryContext, unsigned int port) {
     auto server = RpcServer::make();
@@ -64,20 +75,47 @@
     return true;
 }
 
-bool RunVsockRpcServerCallback(AIBinder* service, unsigned int port,
-                               void (*readyCallback)(void* param), void* param) {
+ARpcServer* ARpcServer_newVsock(AIBinder* service, unsigned int port) {
     auto server = RpcServer::make();
     if (status_t status = server->setupVsockServer(port); status != OK) {
         LOG(ERROR) << "Failed to set up vsock server with port " << port
                    << " error: " << statusToString(status).c_str();
-        return false;
+        return nullptr;
     }
-    RunRpcServer(server, service, readyCallback, param);
-    return true;
+    server->setRootObject(AIBinder_toPlatformBinder(service));
+    return createRpcServerHandle(server);
 }
 
-bool RunVsockRpcServer(AIBinder* service, unsigned int port) {
-    return RunVsockRpcServerCallback(service, port, nullptr, nullptr);
+ARpcServer* ARpcServer_newInitUnixDomain(AIBinder* service, const char* name) {
+    auto server = RpcServer::make();
+    auto fd = unique_fd(android_get_control_socket(name));
+    if (!fd.ok()) {
+        LOG(ERROR) << "Failed to get fd for the socket:" << name;
+        return nullptr;
+    }
+    if (status_t status = server->setupRawSocketServer(std::move(fd)); status != OK) {
+        LOG(ERROR) << "Failed to set up Unix Domain RPC server with name " << name
+                   << " error: " << statusToString(status).c_str();
+        return nullptr;
+    }
+    server->setRootObject(AIBinder_toPlatformBinder(service));
+    return createRpcServerHandle(server);
+}
+
+void ARpcServer_start(ARpcServer* handle) {
+    toRpcServer(handle)->start();
+}
+
+void ARpcServer_join(ARpcServer* handle) {
+    toRpcServer(handle)->join();
+}
+
+void ARpcServer_shutdown(ARpcServer* handle) {
+    toRpcServer(handle)->shutdown();
+}
+
+void ARpcServer_free(ARpcServer* handle) {
+    freeRpcServerHandle(handle);
 }
 
 AIBinder* VsockRpcClient(unsigned int cid, unsigned int port) {
@@ -90,23 +128,6 @@
     return AIBinder_fromPlatformBinder(session->getRootObject());
 }
 
-bool RunInitUnixDomainRpcServer(AIBinder* service, const char* name,
-                                void (*readyCallback)(void* param), void* param) {
-    auto server = RpcServer::make();
-    auto fd = unique_fd(android_get_control_socket(name));
-    if (!fd.ok()) {
-        LOG(ERROR) << "Failed to get fd for the socket:" << name;
-        return false;
-    }
-    if (status_t status = server->setupRawSocketServer(std::move(fd)); status != OK) {
-        LOG(ERROR) << "Failed to set up Unix Domain RPC server with name " << name
-                   << " error: " << statusToString(status).c_str();
-        return false;
-    }
-    RunRpcServer(server, service, readyCallback, param);
-    return true;
-}
-
 AIBinder* UnixDomainRpcClient(const char* name) {
     std::string pathname(name);
     pathname = ANDROID_SOCKET_DIR "/" + pathname;
diff --git a/libs/binder/libbinder_rpc_unstable.map.txt b/libs/binder/libbinder_rpc_unstable.map.txt
index f9c7bcf..1bc2416 100644
--- a/libs/binder/libbinder_rpc_unstable.map.txt
+++ b/libs/binder/libbinder_rpc_unstable.map.txt
@@ -1,9 +1,12 @@
 LIBBINDER_RPC_UNSTABLE_SHIM { # platform-only
   global:
-    RunVsockRpcServer;
-    RunVsockRpcServerCallback;
+    ARpcServer_free;
+    ARpcServer_join;
+    ARpcServer_newInitUnixDomain;
+    ARpcServer_newVsock;
+    ARpcServer_shutdown;
+    ARpcServer_start;
     VsockRpcClient;
-    RunInitUnixDomainRpcServer;
     UnixDomainRpcClient;
     RpcPreconnectedClient;
   local:
diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h
index c234270..ad4188f 100644
--- a/libs/binder/ndk/include_platform/android/binder_manager.h
+++ b/libs/binder/ndk/include_platform/android/binder_manager.h
@@ -68,6 +68,7 @@
  *
  * \param instance identifier of the service used to lookup the service.
  */
+[[deprecated("this polls 5s, use AServiceManager_waitForService or AServiceManager_checkService")]]
 __attribute__((warn_unused_result)) AIBinder* AServiceManager_getService(const char* instance)
         __INTRODUCED_IN(29);
 
@@ -108,6 +109,67 @@
         __INTRODUCED_IN(31);
 
 /**
+ * Function to call when a service is registered. The instance is passed as well as
+ * ownership of the binder named 'registered'.
+ *
+ * WARNING: a lock is held when this method is called in order to prevent races with
+ * AServiceManager_NotificationRegistration_delete. Do not make synchronous binder calls when
+ * implementing this method to avoid deadlocks.
+ *
+ * \param instance instance name of service registered
+ * \param registered ownership-passed instance of service registered
+ * \param cookie data passed during registration for notifications
+ */
+typedef void (*AServiceManager_onRegister)(const char* instance, AIBinder* registered,
+                                           void* cookie);
+
+/**
+ * Represents a registration to servicemanager which can be cleared anytime.
+ */
+struct AServiceManager_NotificationRegistration;
+
+/**
+ * Get notifications when a service is registered. If the service is already registered,
+ * you will immediately get a notification.
+ *
+ * WARNING: it is strongly recommended to use AServiceManager_waitForService API instead.
+ * That API will wait synchronously, which is what you usually want in cases, including
+ * using some feature or during boot up. There is a history of bugs where waiting for
+ * notifications like this races with service startup. Also, when this API is used, a service
+ * bug will result in silent failure (rather than a debuggable deadlock). Furthermore, there
+ * is a history of this API being used to know when a service is up as a proxy for whethre
+ * that service should be started. This should only be used if you are intending to get
+ * ahold of the service as a client. For lazy services, whether a service is registered
+ * should not be used as a proxy for when it should be registered, which is only known
+ * by the real client.
+ *
+ * WARNING: if you use this API, you must also ensure that you check missing services are
+ * started and crash otherwise. If service failures are ignored, the system rots.
+ *
+ * \param instance name of service to wait for notifications about
+ * \param onRegister callback for when service is registered
+ * \param cookie data associated with this callback
+ *
+ * \return the token for this registration. Deleting this token will unregister.
+ */
+__attribute__((warn_unused_result)) AServiceManager_NotificationRegistration*
+AServiceManager_registerForServiceNotifications(const char* instance,
+                                                AServiceManager_onRegister onRegister, void* cookie)
+        __INTRODUCED_IN(34);
+
+/**
+ * Unregister for notifications and delete the object.
+ *
+ * After this method is called, the callback is guaranteed to no longer be invoked. This will block
+ * until any in-progress onRegister callbacks have completed. It is therefore safe to immediately
+ * destroy the void* cookie that was registered when this method returns.
+ *
+ * \param notification object to dismiss
+ */
+void AServiceManager_NotificationRegistration_delete(
+        AServiceManager_NotificationRegistration* notification) __INTRODUCED_IN(34);
+
+/**
  * Check if a service is declared (e.g. VINTF manifest).
  *
  * \param instance identifier of the service.
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index 32ca564..5c7005c 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -155,6 +155,8 @@
 LIBBINDER_NDK34 { # introduced=UpsideDownCake
   global:
     AServiceManager_getUpdatableApexName; # systemapi
+    AServiceManager_registerForServiceNotifications; # systemapi llndk
+    AServiceManager_NotificationRegistration_delete; # systemapi llndk
 };
 
 LIBBINDER_NDK_PLATFORM {
diff --git a/libs/binder/ndk/service_manager.cpp b/libs/binder/ndk/service_manager.cpp
index a12d0e9..e107c83 100644
--- a/libs/binder/ndk/service_manager.cpp
+++ b/libs/binder/ndk/service_manager.cpp
@@ -28,6 +28,7 @@
 using ::android::IServiceManager;
 using ::android::sp;
 using ::android::status_t;
+using ::android::statusToString;
 using ::android::String16;
 using ::android::String8;
 
@@ -86,6 +87,67 @@
     AIBinder_incStrong(ret.get());
     return ret.get();
 }
+typedef void (*AServiceManager_onRegister)(const char* instance, AIBinder* registered,
+                                           void* cookie);
+
+struct AServiceManager_NotificationRegistration
+    : public IServiceManager::LocalRegistrationCallback {
+    std::mutex m;
+    const char* instance = nullptr;
+    void* cookie = nullptr;
+    AServiceManager_onRegister onRegister = nullptr;
+
+    virtual void onServiceRegistration(const String16& smInstance, const sp<IBinder>& binder) {
+        std::lock_guard<std::mutex> l(m);
+        if (onRegister == nullptr) return;
+
+        CHECK_EQ(String8(smInstance), instance);
+
+        sp<AIBinder> ret = ABpBinder::lookupOrCreateFromBinder(binder);
+        AIBinder_incStrong(ret.get());
+
+        onRegister(instance, ret.get(), cookie);
+    }
+
+    void clear() {
+        std::lock_guard<std::mutex> l(m);
+        instance = nullptr;
+        cookie = nullptr;
+        onRegister = nullptr;
+    }
+};
+
+__attribute__((warn_unused_result)) AServiceManager_NotificationRegistration*
+AServiceManager_registerForServiceNotifications(const char* instance,
+                                                AServiceManager_onRegister onRegister,
+                                                void* cookie) {
+    CHECK_NE(instance, nullptr);
+    CHECK_NE(onRegister, nullptr) << instance;
+    // cookie can be nullptr
+
+    auto cb = sp<AServiceManager_NotificationRegistration>::make();
+    cb->instance = instance;
+    cb->onRegister = onRegister;
+    cb->cookie = cookie;
+
+    sp<IServiceManager> sm = defaultServiceManager();
+    if (status_t res = sm->registerForNotifications(String16(instance), cb); res != STATUS_OK) {
+        LOG(ERROR) << "Failed to register for service notifications for " << instance << ": "
+                   << statusToString(res);
+        return nullptr;
+    }
+
+    cb->incStrong(nullptr);
+    return cb.get();
+}
+
+void AServiceManager_NotificationRegistration_delete(
+        AServiceManager_NotificationRegistration* notification) {
+    CHECK_NE(notification, nullptr);
+    notification->clear();
+    notification->decStrong(nullptr);
+}
+
 bool AServiceManager_isDeclared(const char* instance) {
     if (instance == nullptr) {
         return false;
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index e221e4c..9d5ef68 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -254,6 +254,47 @@
     AIBinder_decStrong(binder);
 }
 
+struct ServiceData {
+    std::string instance;
+    ndk::SpAIBinder binder;
+
+    static void fillOnRegister(const char* instance, AIBinder* binder, void* cookie) {
+        ServiceData* d = reinterpret_cast<ServiceData*>(cookie);
+        d->instance = instance;
+        d->binder = ndk::SpAIBinder(binder);
+    }
+};
+
+TEST(NdkBinder, RegisterForServiceNotificationsNonExisting) {
+    ServiceData data;
+    auto* notif = AServiceManager_registerForServiceNotifications(
+            "DOES_NOT_EXIST", ServiceData::fillOnRegister, (void*)&data);
+    ASSERT_NE(notif, nullptr);
+
+    sleep(1);  // give us a chance to fail
+    AServiceManager_NotificationRegistration_delete(notif);
+
+    // checking after deleting to avoid needing a mutex over the data - otherwise
+    // in an environment w/ multiple threads, you would need to guard access
+    EXPECT_EQ(data.instance, "");
+    EXPECT_EQ(data.binder, nullptr);
+}
+
+TEST(NdkBinder, RegisterForServiceNotificationsExisting) {
+    ServiceData data;
+    auto* notif = AServiceManager_registerForServiceNotifications(
+            kExistingNonNdkService, ServiceData::fillOnRegister, (void*)&data);
+    ASSERT_NE(notif, nullptr);
+
+    sleep(1);  // give us a chance to fail
+    AServiceManager_NotificationRegistration_delete(notif);
+
+    // checking after deleting to avoid needing a mutex over the data - otherwise
+    // in an environment w/ multiple threads, you would need to guard access
+    EXPECT_EQ(data.instance, kExistingNonNdkService);
+    EXPECT_EQ(data.binder, ndk::SpAIBinder(AServiceManager_checkService(kExistingNonNdkService)));
+}
+
 TEST(NdkBinder, UnimplementedDump) {
     sp<IFoo> foo = IFoo::getService(IFoo::kSomeInstanceName);
     ASSERT_NE(foo, nullptr);
diff --git a/libs/binder/rust/rpcbinder/Android.bp b/libs/binder/rust/rpcbinder/Android.bp
index 9771cc9..f70ebfc 100644
--- a/libs/binder/rust/rpcbinder/Android.bp
+++ b/libs/binder/rust/rpcbinder/Android.bp
@@ -19,6 +19,7 @@
         "libbinder_rpc_unstable_bindgen_sys",
         "libbinder_rs",
         "libdowncast_rs",
+        "libforeign_types",
         "liblibc",
         "liblog_rust",
     ],
diff --git a/libs/binder/rust/rpcbinder/src/lib.rs b/libs/binder/rust/rpcbinder/src/lib.rs
index 89a49a4..1b719aa 100644
--- a/libs/binder/rust/rpcbinder/src/lib.rs
+++ b/libs/binder/rust/rpcbinder/src/lib.rs
@@ -23,6 +23,4 @@
     get_preconnected_rpc_interface, get_preconnected_rpc_service, get_unix_domain_rpc_interface,
     get_unix_domain_rpc_service, get_vsock_rpc_interface, get_vsock_rpc_service,
 };
-pub use server::{
-    run_init_unix_domain_rpc_server, run_vsock_rpc_server, run_vsock_rpc_server_with_factory,
-};
+pub use server::{run_vsock_rpc_server_with_factory, RpcServer, RpcServerRef};
diff --git a/libs/binder/rust/rpcbinder/src/server.rs b/libs/binder/rust/rpcbinder/src/server.rs
index b350a13..42f5567 100644
--- a/libs/binder/rust/rpcbinder/src/server.rs
+++ b/libs/binder/rust/rpcbinder/src/server.rs
@@ -18,114 +18,89 @@
     unstable_api::{AIBinder, AsNative},
     SpIBinder,
 };
+use binder_rpc_unstable_bindgen::ARpcServer;
+use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
+use std::io::{Error, ErrorKind};
 use std::{ffi::CString, os::raw, ptr::null_mut};
 
-/// Runs a binder RPC server, serving the supplied binder service implementation on the given vsock
-/// port.
-///
-/// If and when the server is ready for connections (it is listening on the port), `on_ready` is
-/// called to allow appropriate action to be taken - e.g. to notify clients that they may now
-/// attempt to connect.
-///
-/// The current thread is joined to the binder thread pool to handle incoming messages.
-///
-/// Returns true if the server has shutdown normally, false if it failed in some way.
-pub fn run_vsock_rpc_server<F>(service: SpIBinder, port: u32, on_ready: F) -> bool
-where
-    F: FnOnce(),
-{
-    let mut ready_notifier = ReadyNotifier(Some(on_ready));
-    ready_notifier.run_vsock_server(service, port)
+foreign_type! {
+    type CType = binder_rpc_unstable_bindgen::ARpcServer;
+    fn drop = binder_rpc_unstable_bindgen::ARpcServer_free;
+
+    /// A type that represents a foreign instance of RpcServer.
+    #[derive(Debug)]
+    pub struct RpcServer;
+    /// A borrowed RpcServer.
+    pub struct RpcServerRef;
 }
 
-/// Runs a binder RPC server, serving the supplied binder service implementation on the given
-/// socket file name. The socket should be initialized in init.rc with the same name.
-///
-/// If and when the server is ready for connections, `on_ready` is called to allow appropriate
-/// action to be taken - e.g. to notify clients that they may now attempt to connect.
-///
-/// The current thread is joined to the binder thread pool to handle incoming messages.
-///
-/// Returns true if the server has shutdown normally, false if it failed in some way.
-pub fn run_init_unix_domain_rpc_server<F>(
-    service: SpIBinder,
-    socket_name: &str,
-    on_ready: F,
-) -> bool
-where
-    F: FnOnce(),
-{
-    let mut ready_notifier = ReadyNotifier(Some(on_ready));
-    ready_notifier.run_init_unix_domain_server(service, socket_name)
-}
+/// SAFETY - The opaque handle can be cloned freely.
+unsafe impl Send for RpcServer {}
+/// SAFETY - The underlying C++ RpcServer class is thread-safe.
+unsafe impl Sync for RpcServer {}
 
-struct ReadyNotifier<F>(Option<F>)
-where
-    F: FnOnce();
-
-impl<F> ReadyNotifier<F>
-where
-    F: FnOnce(),
-{
-    fn run_vsock_server(&mut self, mut service: SpIBinder, port: u32) -> bool {
+impl RpcServer {
+    /// Creates a binder RPC server, serving the supplied binder service implementation on the given
+    /// vsock port.
+    pub fn new_vsock(mut service: SpIBinder, port: u32) -> Result<RpcServer, Error> {
         let service = service.as_native_mut();
-        let param = self.as_void_ptr();
 
         // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
         // Plus the binder objects are threadsafe.
-        // RunVsockRpcServerCallback does not retain a reference to `ready_callback` or `param`; it only
-        // uses them before it returns, which is during the lifetime of `self`.
         unsafe {
-            binder_rpc_unstable_bindgen::RunVsockRpcServerCallback(
-                service,
-                port,
-                Some(Self::ready_callback),
-                param,
-            )
+            Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newVsock(service, port))
         }
     }
 
-    fn run_init_unix_domain_server(&mut self, mut service: SpIBinder, socket_name: &str) -> bool {
+    /// Creates a binder RPC server, serving the supplied binder service implementation on the given
+    /// socket file name. The socket should be initialized in init.rc with the same name.
+    pub fn new_init_unix_domain(
+        mut service: SpIBinder,
+        socket_name: &str,
+    ) -> Result<RpcServer, Error> {
         let socket_name = match CString::new(socket_name) {
             Ok(s) => s,
             Err(e) => {
                 log::error!("Cannot convert {} to CString. Error: {:?}", socket_name, e);
-                return false;
+                return Err(Error::from(ErrorKind::InvalidInput));
             }
         };
         let service = service.as_native_mut();
-        let param = self.as_void_ptr();
 
         // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
         // Plus the binder objects are threadsafe.
-        // RunInitUnixDomainRpcServer does not retain a reference to `ready_callback` or `param`;
-        // it only uses them before it returns, which is during the lifetime of `self`.
         unsafe {
-            binder_rpc_unstable_bindgen::RunInitUnixDomainRpcServer(
+            Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newInitUnixDomain(
                 service,
                 socket_name.as_ptr(),
-                Some(Self::ready_callback),
-                param,
-            )
+            ))
         }
     }
 
-    fn as_void_ptr(&mut self) -> *mut raw::c_void {
-        self as *mut _ as *mut raw::c_void
-    }
-
-    unsafe extern "C" fn ready_callback(param: *mut raw::c_void) {
-        // SAFETY: This is only ever called by `RunVsockRpcServerCallback`, within the lifetime of the
-        // `ReadyNotifier`, with `param` taking the value returned by `as_void_ptr` (so a properly
-        // aligned non-null pointer to an initialized instance).
-        let ready_notifier = param as *mut Self;
-        ready_notifier.as_mut().unwrap().notify()
-    }
-
-    fn notify(&mut self) {
-        if let Some(on_ready) = self.0.take() {
-            on_ready();
+    unsafe fn checked_from_ptr(ptr: *mut ARpcServer) -> Result<RpcServer, Error> {
+        if ptr.is_null() {
+            return Err(Error::new(ErrorKind::Other, "Failed to start server"));
         }
+        Ok(RpcServer::from_ptr(ptr))
+    }
+}
+
+impl RpcServerRef {
+    /// Starts a new background thread and calls join(). Returns immediately.
+    pub fn start(&self) {
+        unsafe { binder_rpc_unstable_bindgen::ARpcServer_start(self.as_ptr()) };
+    }
+
+    /// Joins the RpcServer thread. The call blocks until the server terminates.
+    /// This must be called from exactly one thread.
+    pub fn join(&self) {
+        unsafe { binder_rpc_unstable_bindgen::ARpcServer_join(self.as_ptr()) };
+    }
+
+    /// Shuts down the running RpcServer. Can be called multiple times and from
+    /// multiple threads. Called automatically during drop().
+    pub fn shutdown(&self) {
+        unsafe { binder_rpc_unstable_bindgen::ARpcServer_shutdown(self.as_ptr()) };
     }
 }
 
diff --git a/libs/fakeservicemanager/ServiceManager.cpp b/libs/fakeservicemanager/ServiceManager.cpp
index 480ec79..1109ad8 100644
--- a/libs/fakeservicemanager/ServiceManager.cpp
+++ b/libs/fakeservicemanager/ServiceManager.cpp
@@ -36,6 +36,9 @@
 status_t ServiceManager::addService(const String16& name, const sp<IBinder>& service,
                                 bool /*allowIsolated*/,
                                 int /*dumpsysFlags*/) {
+    if (service == nullptr) {
+        return UNEXPECTED_NULL;
+    }
     mNameToService[name] = service;
     return NO_ERROR;
 }
@@ -103,4 +106,8 @@
     std::vector<IServiceManager::ServiceDebugInfo> ret;
     return ret;
 }
+
+void ServiceManager::clear() {
+    mNameToService.clear();
+}
 }  // namespace android
diff --git a/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h b/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h
index ee0637e..ba6bb7d 100644
--- a/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h
+++ b/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h
@@ -64,6 +64,9 @@
 
     std::vector<IServiceManager::ServiceDebugInfo> getServiceDebugInfo() override;
 
+    // Clear all of the registered services
+    void clear();
+
 private:
     std::map<String16, sp<IBinder>> mNameToService;
 };
diff --git a/libs/fakeservicemanager/test_sm.cpp b/libs/fakeservicemanager/test_sm.cpp
index 71e5abe..8682c1c 100644
--- a/libs/fakeservicemanager/test_sm.cpp
+++ b/libs/fakeservicemanager/test_sm.cpp
@@ -50,6 +50,12 @@
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK);
 }
 
+TEST(AddService, SadNullBinder) {
+    auto sm = new ServiceManager();
+    EXPECT_EQ(sm->addService(String16("foo"), nullptr, false /*allowIsolated*/,
+        IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), android::UNEXPECTED_NULL);
+}
+
 TEST(AddService, HappyOverExistingService) {
     auto sm = new ServiceManager();
     EXPECT_EQ(sm->addService(String16("foo"), getBinder(), false /*allowIsolated*/,
@@ -58,6 +64,15 @@
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK);
 }
 
+TEST(AddService, HappyClearAddedService) {
+    auto sm = new ServiceManager();
+    EXPECT_EQ(sm->addService(String16("foo"), getBinder(), false /*allowIsolated*/,
+        IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK);
+    EXPECT_NE(sm->getService(String16("foo")), nullptr);
+    sm->clear();
+    EXPECT_EQ(sm->getService(String16("foo")), nullptr);
+}
+
 TEST(GetService, HappyHappy) {
     auto sm = new ServiceManager();
     sp<IBinder> service = getBinder();
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index df0b271..8e57152 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -22,6 +22,7 @@
         "flags_test.cpp",
         "future_test.cpp",
         "match_test.cpp",
+        "mixins_test.cpp",
         "non_null_test.cpp",
         "optional_test.cpp",
         "shared_mutex_test.cpp",
diff --git a/libs/ftl/mixins_test.cpp b/libs/ftl/mixins_test.cpp
new file mode 100644
index 0000000..2c9f9df
--- /dev/null
+++ b/libs/ftl/mixins_test.cpp
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ftl/mixins.h>
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+namespace android::test {
+namespace {
+
+// Keep in sync with example usage in header file.
+
+struct Id : ftl::Constructible<Id, std::int32_t>, ftl::Equatable<Id> {
+  using Constructible::Constructible;
+};
+
+static_assert(!std::is_default_constructible_v<Id>);
+
+struct Color : ftl::DefaultConstructible<Color, std::uint8_t>,
+               ftl::Equatable<Color>,
+               ftl::Orderable<Color> {
+  using DefaultConstructible::DefaultConstructible;
+};
+
+static_assert(Color() == Color(0u));
+static_assert(ftl::to_underlying(Color(-1)) == 255u);
+static_assert(Color(1u) < Color(2u));
+
+struct Sequence : ftl::DefaultConstructible<Sequence, std::int8_t, -1>,
+                  ftl::Equatable<Sequence>,
+                  ftl::Orderable<Sequence>,
+                  ftl::Incrementable<Sequence> {
+  using DefaultConstructible::DefaultConstructible;
+};
+
+static_assert(Sequence() == Sequence(-1));
+
+struct Timeout : ftl::DefaultConstructible<Timeout, std::chrono::seconds, 10>,
+                 ftl::Equatable<Timeout>,
+                 ftl::Addable<Timeout> {
+  using DefaultConstructible::DefaultConstructible;
+};
+
+using namespace std::chrono_literals;
+static_assert(Timeout() + Timeout(5s) == Timeout(15s));
+
+// Construction.
+constexpr Id kId{1234};
+constexpr Sequence kSequence;
+
+// Underlying value.
+static_assert(ftl::to_underlying(Id(-42)) == -42);
+static_assert(ftl::to_underlying(kSequence) == -1);
+
+// Casting.
+static_assert(static_cast<std::int32_t>(Id(-1)) == -1);
+static_assert(static_cast<std::int8_t>(kSequence) == -1);
+
+static_assert(!std::is_convertible_v<std::int32_t, Id>);
+static_assert(!std::is_convertible_v<Id, std::int32_t>);
+
+// Equality.
+static_assert(kId == Id(1234));
+static_assert(kId != Id(123));
+static_assert(kSequence == Sequence(-1));
+
+// Ordering.
+static_assert(Sequence(1) < Sequence(2));
+static_assert(Sequence(2) > Sequence(1));
+static_assert(Sequence(3) <= Sequence(4));
+static_assert(Sequence(4) >= Sequence(3));
+static_assert(Sequence(5) <= Sequence(5));
+static_assert(Sequence(6) >= Sequence(6));
+
+// Incrementing.
+template <typename Op, typename T, typename... Ts>
+constexpr auto mutable_op(Op op, T lhs, Ts... rhs) {
+  const T result = op(lhs, rhs...);
+  return std::make_pair(lhs, result);
+}
+
+static_assert(mutable_op([](auto& lhs) { return ++lhs; }, Sequence()) ==
+              std::make_pair(Sequence(0), Sequence(0)));
+
+static_assert(mutable_op([](auto& lhs) { return lhs++; }, Sequence()) ==
+              std::make_pair(Sequence(0), Sequence(-1)));
+
+// Addition.
+
+// `Addable` implies `Incrementable`.
+static_assert(mutable_op([](auto& lhs) { return ++lhs; }, Timeout()) ==
+              std::make_pair(Timeout(11s), Timeout(11s)));
+
+static_assert(mutable_op([](auto& lhs) { return lhs++; }, Timeout()) ==
+              std::make_pair(Timeout(11s), Timeout(10s)));
+
+static_assert(Timeout(5s) + Timeout(6s) == Timeout(11s));
+
+static_assert(mutable_op([](auto& lhs, const auto& rhs) { return lhs += rhs; }, Timeout(7s),
+                         Timeout(8s)) == std::make_pair(Timeout(15s), Timeout(15s)));
+
+// Type safety.
+
+namespace traits {
+
+template <typename, typename = void>
+struct is_incrementable : std::false_type {};
+
+template <typename T>
+struct is_incrementable<T, std::void_t<decltype(++std::declval<T&>())>> : std::true_type {};
+
+template <typename T>
+constexpr bool is_incrementable_v = is_incrementable<T>{};
+
+template <typename, typename, typename, typename = void>
+struct has_binary_op : std::false_type {};
+
+template <typename Op, typename T, typename U>
+struct has_binary_op<Op, T, U, std::void_t<decltype(Op{}(std::declval<T&>(), std::declval<U&>()))>>
+    : std::true_type {};
+
+template <typename T, typename U>
+constexpr bool is_equatable_v =
+    has_binary_op<std::equal_to<void>, T, U>{} && has_binary_op<std::not_equal_to<void>, T, U>{};
+
+template <typename T, typename U>
+constexpr bool is_orderable_v =
+    has_binary_op<std::less<void>, T, U>{} && has_binary_op<std::less_equal<void>, T, U>{} &&
+    has_binary_op<std::greater<void>, T, U>{} && has_binary_op<std::greater_equal<void>, T, U>{};
+
+template <typename T, typename U>
+constexpr bool is_addable_v = has_binary_op<std::plus<void>, T, U>{};
+
+}  // namespace traits
+
+struct Real : ftl::Constructible<Real, float> {
+  using Constructible::Constructible;
+};
+
+static_assert(traits::is_equatable_v<Id, Id>);
+static_assert(!traits::is_equatable_v<Real, Real>);
+static_assert(!traits::is_equatable_v<Id, Color>);
+static_assert(!traits::is_equatable_v<Sequence, Id>);
+static_assert(!traits::is_equatable_v<Id, std::int32_t>);
+static_assert(!traits::is_equatable_v<std::chrono::seconds, Timeout>);
+
+static_assert(traits::is_orderable_v<Color, Color>);
+static_assert(!traits::is_orderable_v<Id, Id>);
+static_assert(!traits::is_orderable_v<Real, Real>);
+static_assert(!traits::is_orderable_v<Color, Sequence>);
+static_assert(!traits::is_orderable_v<Color, std::uint8_t>);
+static_assert(!traits::is_orderable_v<std::chrono::seconds, Timeout>);
+
+static_assert(traits::is_incrementable_v<Sequence>);
+static_assert(traits::is_incrementable_v<Timeout>);
+static_assert(!traits::is_incrementable_v<Id>);
+static_assert(!traits::is_incrementable_v<Color>);
+static_assert(!traits::is_incrementable_v<Real>);
+
+static_assert(traits::is_addable_v<Timeout, Timeout>);
+static_assert(!traits::is_addable_v<Id, Id>);
+static_assert(!traits::is_addable_v<Real, Real>);
+static_assert(!traits::is_addable_v<Sequence, Sequence>);
+static_assert(!traits::is_addable_v<Timeout, Sequence>);
+static_assert(!traits::is_addable_v<Color, Timeout>);
+
+}  // namespace
+}  // namespace android::test
diff --git a/libs/ftl/optional_test.cpp b/libs/ftl/optional_test.cpp
index f7410c2..6b3b6c4 100644
--- a/libs/ftl/optional_test.cpp
+++ b/libs/ftl/optional_test.cpp
@@ -164,4 +164,35 @@
                      }));
 }
 
+// Comparison.
+namespace {
+
+constexpr Optional<int> kOptional1 = 1;
+constexpr Optional<int> kAnotherOptional1 = 1;
+constexpr Optional<int> kOptional2 = 2;
+constexpr Optional<int> kOptionalEmpty, kAnotherOptionalEmpty;
+
+constexpr std::optional<int> kStdOptional1 = 1;
+
+static_assert(kOptional1 == kAnotherOptional1);
+
+static_assert(kOptional1 != kOptional2);
+static_assert(kOptional2 != kOptional1);
+
+static_assert(kOptional1 != kOptionalEmpty);
+static_assert(kOptionalEmpty != kOptional1);
+
+static_assert(kOptionalEmpty == kAnotherOptionalEmpty);
+
+static_assert(kOptional1 == kStdOptional1);
+static_assert(kStdOptional1 == kOptional1);
+
+static_assert(kOptional2 != kStdOptional1);
+static_assert(kStdOptional1 != kOptional2);
+
+static_assert(kOptional2 != kOptionalEmpty);
+static_assert(kOptionalEmpty != kOptional2);
+
+} // namespace
+
 }  // namespace android::test
diff --git a/libs/gui/aidl/android/gui/OverlayProperties.aidl b/libs/gui/aidl/android/gui/OverlayProperties.aidl
index 80d5ced..75cea15 100644
--- a/libs/gui/aidl/android/gui/OverlayProperties.aidl
+++ b/libs/gui/aidl/android/gui/OverlayProperties.aidl
@@ -16,9 +16,11 @@
 
 package android.gui;
 
-import android.gui.SupportedBufferCombinations;
-
 /** @hide */
 parcelable OverlayProperties {
+    parcelable SupportedBufferCombinations {
+        int[] pixelFormats;
+        int[] dataspaces;
+    }
     SupportedBufferCombinations[] combinations;
 }
diff --git a/libs/gui/include/gui/LayerMetadata.h b/libs/gui/include/gui/LayerMetadata.h
index e16f89c..9cf62bc 100644
--- a/libs/gui/include/gui/LayerMetadata.h
+++ b/libs/gui/include/gui/LayerMetadata.h
@@ -66,8 +66,9 @@
     Standard = 1,
     Performance = 2,
     Battery = 3,
+    Custom = 4,
 
-    ftl_last = Battery
+    ftl_last = Custom
 };
 
 } // namespace android::gui
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index ac74c8a..b01a3db 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -272,6 +272,7 @@
     WindowInfoHandle(const WindowInfo& other);
 
     inline const WindowInfo* getInfo() const { return &mInfo; }
+    inline WindowInfo* editInfo() { return &mInfo; }
 
     sp<IBinder> getToken() const;
 
diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp
index 0375915..54af7c9 100644
--- a/libs/jpegrecoverymap/Android.bp
+++ b/libs/jpegrecoverymap/Android.bp
@@ -31,6 +31,7 @@
     srcs: [
         "recoverymap.cpp",
         "recoverymapmath.cpp",
+        "recoverymaputils.cpp",
     ],
 
     shared_libs: [
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
index df24b10..5c9c8b6 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
@@ -61,6 +61,21 @@
      * decompressImage().
      */
     size_t getDecompressedImageHeight();
+    /*
+     * Returns the XMP data from the image.
+     */
+    void* getXMPPtr();
+    /*
+     * Returns the decompressed XMP buffer size. This method must be called only after
+     * calling decompressImage().
+     */
+    size_t getXMPSize();
+
+    bool getCompressedImageParameters(const void* image, int length,
+                              size_t* pWidth, size_t* pHeight,
+                              std::vector<uint8_t>* &iccData,
+                              std::vector<uint8_t>* &exifData);
+
 private:
     bool decode(const void* image, int length);
     // Returns false if errors occur.
@@ -72,6 +87,9 @@
     static const int kCompressBatchSize = 16;
     // The buffer that holds the decompressed result.
     std::vector<JOCTET> mResultBuffer;
+    // The buffer that holds XMP Data.
+    std::vector<JOCTET> mXMPBuffer;
+
     // Resolution of the decompressed image.
     size_t mWidth;
     size_t mHeight;
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
index 194cd2f..9f53a57 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
@@ -37,6 +37,7 @@
     ERROR_JPEGR_INVALID_NULL_PTR        = JPEGR_IO_ERROR_BASE - 2,
     ERROR_JPEGR_RESOLUTION_MISMATCH     = JPEGR_IO_ERROR_BASE - 3,
     ERROR_JPEGR_BUFFER_TOO_SMALL        = JPEGR_IO_ERROR_BASE - 4,
+    ERROR_JPEGR_INVALID_COLORGAMUT      = JPEGR_IO_ERROR_BASE - 5,
 
     JPEGR_RUNTIME_ERROR_BASE            = -20000,
     ERROR_JPEGR_ENCODE_ERROR            = JPEGR_RUNTIME_ERROR_BASE - 1,
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
index b2ca481..5597303 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
@@ -22,11 +22,24 @@
 namespace android::recoverymap {
 
 typedef enum {
-  JPEGR_COLORSPACE_UNSPECIFIED,
-  JPEGR_COLORSPACE_BT709,
-  JPEGR_COLORSPACE_P3,
-  JPEGR_COLORSPACE_BT2100,
-} jpegr_color_space;
+  JPEGR_COLORGAMUT_UNSPECIFIED,
+  JPEGR_COLORGAMUT_BT709,
+  JPEGR_COLORGAMUT_P3,
+  JPEGR_COLORGAMUT_BT2100,
+} jpegr_color_gamut;
+
+// Transfer functions as defined for XMP metadata
+typedef enum {
+  JPEGR_TF_HLG = 0,
+  JPEGR_TF_PQ = 1,
+} jpegr_transfer_function;
+
+struct jpegr_info_struct {
+    size_t width;
+    size_t height;
+    std::vector<uint8_t>* iccData;
+    std::vector<uint8_t>* exifData;
+};
 
 /*
  * Holds information for uncompressed image or recovery map.
@@ -38,8 +51,8 @@
     int width;
     // Height of the recovery map or image in pixels.
     int height;
-    // Color space.
-    jpegr_color_space colorSpace;
+    // Color gamut.
+    jpegr_color_gamut colorGamut;
 };
 
 /*
@@ -48,10 +61,12 @@
 struct jpegr_compressed_struct {
     // Pointer to the data location.
     void* data;
-    // Data length.
+    // Used data length in bytes.
     int length;
-    // Color space.
-    jpegr_color_space colorSpace;
+    // Maximum available data length in bytes.
+    int maxLength;
+    // Color gamut.
+    jpegr_color_gamut colorGamut;
 };
 
 /*
@@ -64,9 +79,52 @@
     int length;
 };
 
+struct chromaticity_coord {
+  float x;
+  float y;
+};
+
+
+struct st2086_metadata {
+  // xy chromaticity coordinate of the red primary of the mastering display
+  chromaticity_coord redPrimary;
+  // xy chromaticity coordinate of the green primary of the mastering display
+  chromaticity_coord greenPrimary;
+  // xy chromaticity coordinate of the blue primary of the mastering display
+  chromaticity_coord bluePrimary;
+  // xy chromaticity coordinate of the white point of the mastering display
+  chromaticity_coord whitePoint;
+  // Maximum luminance in nits of the mastering display
+  uint32_t maxLuminance;
+  // Minimum luminance in nits of the mastering display
+  float minLuminance;
+};
+
+struct hdr10_metadata {
+  // Mastering display color volume
+  st2086_metadata st2086Metadata;
+  // Max frame average light level in nits
+  float maxFALL;
+  // Max content light level in nits
+  float maxCLL;
+};
+
+struct jpegr_metadata {
+  // JPEG/R version
+  uint32_t version;
+  // Range scaling factor for the map
+  float rangeScalingFactor;
+  // The transfer function for decoding the HDR representation of the image
+  jpegr_transfer_function transferFunction;
+  // HDR10 metadata, only applicable for transferFunction of JPEGR_TF_PQ
+  hdr10_metadata hdr10Metadata;
+};
+
 typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr;
 typedef struct jpegr_compressed_struct* jr_compressed_ptr;
 typedef struct jpegr_exif_struct* jr_exif_ptr;
+typedef struct jpegr_metadata* jr_metadata_ptr;
+typedef struct jpegr_info_struct* jr_info_ptr;
 
 class RecoveryMap {
 public:
@@ -75,9 +133,10 @@
      *
      * Generate recovery map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append
      * the recovery map to the end of the compressed JPEG. HDR and SDR inputs must be the same
-     * resolution and color space.
+     * resolution.
      * @param uncompressed_p010_image uncompressed HDR image in P010 color format
      * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+     * @param hdr_tf transfer function of the HDR image
      * @param dest destination of the compressed JPEGR image
      * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
      *                the highest quality
@@ -86,6 +145,7 @@
      */
     status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                          jr_uncompressed_ptr uncompressed_yuv_420_image,
+                         jpegr_transfer_function hdr_tf,
                          jr_compressed_ptr dest,
                          int quality,
                          jr_exif_ptr exif);
@@ -100,12 +160,14 @@
      * @param uncompressed_p010_image uncompressed HDR image in P010 color format
      * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
      * @param compressed_jpeg_image compressed 8-bit JPEG image
+     * @param hdr_tf transfer function of the HDR image
      * @param dest destination of the compressed JPEGR image
      * @return NO_ERROR if encoding succeeds, error code if error occurs.
      */
     status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                          jr_uncompressed_ptr uncompressed_yuv_420_image,
                          jr_compressed_ptr compressed_jpeg_image,
+                         jpegr_transfer_function hdr_tf,
                          jr_compressed_ptr dest);
 
     /*
@@ -115,27 +177,28 @@
      *
      * Decode the compressed 8-bit JPEG image to YUV SDR, generate recovery map from the HDR input
      * and the decoded SDR result, append the recovery map to the end of the compressed JPEG. HDR
-     * and SDR inputs must be the same resolution and color space.
+     * and SDR inputs must be the same resolution.
      * @param uncompressed_p010_image uncompressed HDR image in P010 color format
      * @param compressed_jpeg_image compressed 8-bit JPEG image
+     * @param hdr_tf transfer function of the HDR image
      * @param dest destination of the compressed JPEGR image
      * @return NO_ERROR if encoding succeeds, error code if error occurs.
      */
     status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                          jr_compressed_ptr compressed_jpeg_image,
+                         jpegr_transfer_function hdr_tf,
                          jr_compressed_ptr dest);
 
     /*
      * Decompress JPEGR image.
      *
+     * The output JPEGR image is in RGBA_1010102 data format if decoding to HDR.
      * @param compressed_jpegr_image compressed JPEGR image
      * @param dest destination of the uncompressed JPEGR image
-     * @param exif destination of the decoded EXIF metadata. Default value is nullptr where EXIF
-     *             metadata will not be decoded.
-     * @param request_sdr flag that request SDR output, default to false (request HDR output). If
-     *                    set to true, decoder will only decode the primary image which is SDR.
-     *                    Setting of request_sdr and input source (HDR or SDR) can be found in
-     *                    the table below:
+     * @param exif destination of the decoded EXIF metadata.
+     * @param request_sdr flag that request SDR output. If set to true, decoder will only decode
+     *                    the primary image which is SDR. Setting of request_sdr and input source
+     *                    (HDR or SDR) can be found in the table below:
      *                    |  input source  |  request_sdr  |  output of decoding  |
      *                    |       HDR      |     true      |          SDR         |
      *                    |       HDR      |     false     |          HDR         |
@@ -147,6 +210,17 @@
                          jr_uncompressed_ptr dest,
                          jr_exif_ptr exif = nullptr,
                          bool request_sdr = false);
+
+    /*
+    * Gets Info from JPEGR file without decoding it.
+    *
+    * The output is filled jpegr_info structure
+    * @param compressed_jpegr_image compressed JPEGR image
+    * @param jpegr_info pointer to output JPEGR info
+    * @return NO_ERROR if JPEGR parsing succeeds, error code otherwise
+    */
+    status_t getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
+                          jr_info_ptr jpegr_info);
 private:
     /*
      * This method is called in the decoding pipeline. It will decode the recovery map.
@@ -176,29 +250,46 @@
      * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
      * @param uncompressed_p010_image uncompressed HDR image in P010 color format
      * @param dest recovery map; caller responsible for memory of data
-     * @param hdr_ratio HDR ratio will be updated in this method
+     * @param metadata metadata provides the transfer function for the HDR
+     *                 image; range_scaling_factor and hdr10 FALL and CLL will
+     *                 be updated.
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
     status_t generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
                                  jr_uncompressed_ptr uncompressed_p010_image,
-                                 jr_uncompressed_ptr dest,
-                                 float &hdr_ratio);
+                                 jr_metadata_ptr metadata,
+                                 jr_uncompressed_ptr dest);
 
     /*
      * This method is called in the decoding pipeline. It will take the uncompressed (decoded)
-     * 8-bit yuv image and the uncompressed (decoded) recovery map as input, and calculate the
-     * 10-bit recovered image (in p010 color format).
+     * 8-bit yuv image, the uncompressed (decoded) recovery map, and extracted JPEG/R metadata as
+     * input, and calculate the 10-bit recovered image. The recovered output image is the same
+     * color gamut as the SDR image, with the transfer function specified in the JPEG/R metadata,
+     * and is in RGBA1010102 data format.
      *
      * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
      * @param uncompressed_recovery_map uncompressed recovery map
+     * @param metadata JPEG/R metadata extracted from XMP.
      * @param dest reconstructed HDR image
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
     status_t applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
                               jr_uncompressed_ptr uncompressed_recovery_map,
+                              jr_metadata_ptr metadata,
                               jr_uncompressed_ptr dest);
 
     /*
+     * This methoud is called to separate primary image and recovery map image from JPEGR
+     *
+     * @param compressed_jpegr_image compressed JPEGR image
+     * @param primary_image destination of primary image
+     * @param recovery_map destination of compressed recovery map
+     * @return NO_ERROR if calculation succeeds, error code if error occurs.
+    */
+    status_t extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
+                                               jr_compressed_ptr primary_image,
+                                               jr_compressed_ptr recovery_map);
+    /*
      * This method is called in the decoding pipeline. It will read XMP metadata to find the start
      * position of the compressed recovery map, and will extract the compressed recovery map.
      *
@@ -206,7 +297,8 @@
      * @param dest destination of compressed recovery map
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
-    status_t extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image, jr_compressed_ptr dest);
+    status_t extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
+                                jr_compressed_ptr dest);
 
     /*
      * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image
@@ -215,13 +307,13 @@
      *
      * @param compressed_jpeg_image compressed 8-bit JPEG image
      * @param compress_recovery_map compressed recover map
-     * @param hdr_ratio HDR ratio
+     * @param metadata JPEG/R metadata to encode in XMP of the jpeg
      * @param dest compressed JPEGR image
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
     status_t appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
                                jr_compressed_ptr compressed_recovery_map,
-                               float hdr_ratio,
+                               jr_metadata_ptr metadata,
                                jr_compressed_ptr dest);
 
     /*
@@ -229,7 +321,7 @@
      *
      * below is an example of the XMP metadata that this function generates where
      * secondary_image_length = 1000
-     * hdr_ratio = 1.25
+     * range_scaling_factor = 1.25
      *
      * <x:xmpmeta
      *   xmlns:x="adobe:ns:meta/"
@@ -239,7 +331,7 @@
      *     <rdf:Description
      *       xmlns:GContainer="http://ns.google.com/photos/1.0/container/">
      *       <GContainer:Version>1</GContainer:Version>
-     *       <GContainer:HdrRatio>1.25</GContainer:HdrRatio>
+     *       <GContainer:RangeScalingFactor>1.25</GContainer:RangeScalingFactor>
      *       <GContainer:Directory>
      *         <rdf:Seq>
      *           <rdf:li>
@@ -260,10 +352,10 @@
      * </x:xmpmeta>
      *
      * @param secondary_image_length length of secondary image
-     * @param hdr_ratio hdr ratio
+     * @param metadata JPEG/R metadata to encode as XMP
      * @return XMP metadata in type of string
      */
-    std::string generateXmp(int secondary_image_length, float hdr_ratio);
+    std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata);
 };
 
 } // namespace android::recoverymap
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
index e76bc20..fe7a651 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
@@ -43,6 +43,9 @@
   };
 };
 
+typedef Color (*ColorTransformFn)(Color);
+typedef float (*ColorCalculationFn)(Color);
+
 inline Color operator+=(Color& lhs, const Color& rhs) {
   lhs.r += rhs.r;
   lhs.g += rhs.g;
@@ -138,7 +141,10 @@
 ////////////////////////////////////////////////////////////////////////////////
 // Display-P3 transformations
 
-
+/*
+ * Calculated the luminance of a linear RGB P3 pixel, according to EG 432-1.
+ */
+float p3Luminance(Color e);
 
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -169,10 +175,43 @@
  */
 Color hlgInvOetf(Color e_gamma);
 
+/*
+ * Convert from scene luminance in nits to PQ.
+ */
+Color pqOetf(Color e);
+
+/*
+ * Convert from PQ to scene luminance in nits.
+ */
+Color pqInvOetf(Color e_gamma);
+
+
 ////////////////////////////////////////////////////////////////////////////////
 // Color space conversions
 
+/*
+ * Convert between color spaces with linear RGB data, according to ITU-R BT.2407 and EG 432-1.
+ *
+ * All conversions are derived from multiplying the matrix for XYZ to output RGB color gamut by the
+ * matrix for input RGB color gamut to XYZ. The matrix for converting from XYZ to an RGB gamut is
+ * always the inverse of the RGB gamut to XYZ matrix.
+ */
+Color bt709ToP3(Color e);
+Color bt709ToBt2100(Color e);
+Color p3ToBt709(Color e);
+Color p3ToBt2100(Color e);
+Color bt2100ToBt709(Color e);
+Color bt2100ToP3(Color e);
 
+/*
+ * Identity conversion.
+ */
+inline Color identityConversion(Color e) { return e; }
+
+/*
+ * Get the conversion to apply to the HDR image for recovery map generation
+ */
+ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gamut hdr_gamut);
 
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -220,6 +259,13 @@
  */
 Color sampleP010(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
 
+/*
+ * Convert from Color to RGBA1010102.
+ *
+ * Alpha always set to 1.0.
+ */
+uint32_t colorToRgba1010102(Color e_gamma);
+
 } // namespace android::recoverymap
 
 #endif // ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
new file mode 100644
index 0000000..e35f2d7
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
+#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
+
+#include <stdint.h>
+#include <cstdio>
+
+
+namespace android::recoverymap {
+
+struct jpegr_metadata;
+
+/*
+ * Parses XMP packet and fills metadata with data from XMP
+ *
+ * @param xmp_data pointer to XMP packet
+ * @param xmp_size size of XMP packet
+ * @param metadata place to store HDR metadata values
+ * @return true if metadata is successfully retrieved, false otherwise
+*/
+bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata);
+
+}
+
+#endif //ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoder.cpp
index c1fb6c3..0185e55 100644
--- a/libs/jpegrecoverymap/jpegdecoder.cpp
+++ b/libs/jpegrecoverymap/jpegdecoder.cpp
@@ -20,8 +20,15 @@
 
 #include <errno.h>
 #include <setjmp.h>
+#include <string>
+
+using namespace std;
 
 namespace android::recoverymap {
+
+const uint32_t kExifMarker = JPEG_APP0 + 1;
+const uint32_t kICCMarker = JPEG_APP0 + 2;
+
 struct jpegr_source_mgr : jpeg_source_mgr {
     jpegr_source_mgr(const uint8_t* ptr, int len);
     ~jpegr_source_mgr();
@@ -88,6 +95,7 @@
     }
 
     mResultBuffer.clear();
+    mXMPBuffer.clear();
     if (!decode(image, length)) {
         return false;
     }
@@ -103,6 +111,15 @@
     return mResultBuffer.size();
 }
 
+void* JpegDecoder::getXMPPtr() {
+    return mXMPBuffer.data();
+}
+
+size_t JpegDecoder::getXMPSize() {
+    return mXMPBuffer.size();
+}
+
+
 size_t JpegDecoder::getDecompressedImageWidth() {
     return mWidth;
 }
@@ -115,6 +132,8 @@
     jpeg_decompress_struct cinfo;
     jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
     jpegrerror_mgr myerr;
+    string nameSpace = "http://ns.adobe.com/xap/1.0/";
+
     cinfo.err = jpeg_std_error(&myerr.pub);
     myerr.pub.error_exit = jpegrerror_exit;
 
@@ -124,9 +143,26 @@
     }
     jpeg_create_decompress(&cinfo);
 
+    jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF);
+
     cinfo.src = &mgr;
     jpeg_read_header(&cinfo, TRUE);
 
+    // Save XMP Data
+    for (jpeg_marker_struct* marker = cinfo.marker_list; marker; marker = marker->next) {
+        if (marker->marker == kExifMarker) {
+            const unsigned int len = marker->data_length;
+            if (len > nameSpace.size() &&
+                !strncmp(reinterpret_cast<const char*>(marker->data),
+                         nameSpace.c_str(), nameSpace.size())) {
+                mXMPBuffer.resize(len+1, 0);
+                memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len);
+                break;
+            }
+        }
+    }
+
+
     mWidth = cinfo.image_width;
     mHeight = cinfo.image_height;
 
@@ -161,6 +197,43 @@
     return decompressYUV(cinfo, dest);
 }
 
+bool JpegDecoder::getCompressedImageParameters(const void* image, int length,
+                              size_t *pWidth, size_t *pHeight,
+                              std::vector<uint8_t> *&iccData , std::vector<uint8_t> *&exifData) {
+    jpeg_decompress_struct cinfo;
+    jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
+    jpegrerror_mgr myerr;
+    cinfo.err = jpeg_std_error(&myerr.pub);
+    myerr.pub.error_exit = jpegrerror_exit;
+
+    if (setjmp(myerr.setjmp_buffer)) {
+        jpeg_destroy_decompress(&cinfo);
+        return false;
+    }
+    jpeg_create_decompress(&cinfo);
+
+    jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF);
+    jpeg_save_markers(&cinfo, kICCMarker, 0xFFFF);
+
+    cinfo.src = &mgr;
+    if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
+        jpeg_destroy_decompress(&cinfo);
+        return false;
+    }
+
+    *pWidth = cinfo.image_width;
+    *pHeight = cinfo.image_height;
+
+    //TODO: Parse iccProfile and exifData
+    (void)iccData;
+    (void)exifData;
+
+
+    jpeg_destroy_decompress(&cinfo);
+    return true;
+}
+
+
 bool JpegDecoder::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
 
     JSAMPROW y[kCompressBatchSize];
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index 86eb8a8..4a209ec 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -14,24 +14,27 @@
  * limitations under the License.
  */
 
-// TODO: need to clean up handling around hdr_ratio and passing it around
-// TODO: need to handle color space information; currently we assume everything
-// is srgb in.
-// TODO: handle PQ encode/decode (currently only HLG)
-
 #include <jpegrecoverymap/recoverymap.h>
 #include <jpegrecoverymap/jpegencoder.h>
 #include <jpegrecoverymap/jpegdecoder.h>
 #include <jpegrecoverymap/recoverymapmath.h>
+#include <jpegrecoverymap/recoverymaputils.h>
 
 #include <image_io/jpeg/jpeg_marker.h>
 #include <image_io/xml/xml_writer.h>
+#include <image_io/jpeg/jpeg_info.h>
+#include <image_io/jpeg/jpeg_scanner.h>
+#include <image_io/jpeg/jpeg_info_builder.h>
+#include <image_io/base/data_segment_data_source.h>
+#include <utils/Log.h>
 
 #include <memory>
 #include <sstream>
 #include <string>
+#include <cmath>
 
 using namespace std;
+using namespace photos_editing_formats::image_io;
 
 namespace android::recoverymap {
 
@@ -43,9 +46,23 @@
     }                           \
   }
 
+// The current JPEGR version that we encode to
+static const uint32_t kJpegrVersion = 1;
+
 // Map is quarter res / sixteenth size
 static const size_t kMapDimensionScaleFactor = 4;
+// JPEG compress quality (0 ~ 100) for recovery map
+static const int kMapCompressQuality = 85;
 
+// TODO: fill in st2086 metadata
+static const st2086_metadata kSt2086Metadata = {
+  {0.0f, 0.0f},
+  {0.0f, 0.0f},
+  {0.0f, 0.0f},
+  {0.0f, 0.0f},
+  0,
+  1.0f,
+};
 
 /*
  * Helper function used for generating XMP metadata.
@@ -70,7 +87,7 @@
  * @return status of succeed or error code.
  */
 status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
-  if (position + length > destination->length) {
+  if (position + length > destination->maxLength) {
     return ERROR_JPEGR_BUFFER_TOO_SMALL;
   }
 
@@ -81,6 +98,7 @@
 
 status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                                   jr_uncompressed_ptr uncompressed_yuv_420_image,
+                                  jpegr_transfer_function hdr_tf,
                                   jr_compressed_ptr dest,
                                   int quality,
                                   jr_exif_ptr /* exif */) {
@@ -99,21 +117,27 @@
     return ERROR_JPEGR_RESOLUTION_MISMATCH;
   }
 
+  jpegr_metadata metadata;
+  metadata.version = kJpegrVersion;
+  metadata.transferFunction = hdr_tf;
+  if (hdr_tf == JPEGR_TF_PQ) {
+    metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
+  }
+
   jpegr_uncompressed_struct map;
-  float hdr_ratio = 0.0f;
   JPEGR_CHECK(generateRecoveryMap(
-      uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio));
+      uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(map.data));
 
   jpegr_compressed_struct compressed_map;
-  std::unique_ptr<uint8_t[]> compressed_map_data =
-      std::make_unique<uint8_t[]>(map.width * map.height);
+  compressed_map.maxLength = map.width * map.height;
+  unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
   compressed_map.data = compressed_map_data.get();
   JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
 
   JpegEncoder jpeg_encoder;
-  // TODO: ICC data - need color space information
+  // TODO: determine ICC data based on color gamut information
   if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
                                   uncompressed_yuv_420_image->width,
                                   uncompressed_yuv_420_image->height, quality, nullptr, 0)) {
@@ -123,7 +147,7 @@
   jpeg.data = jpeg_encoder.getCompressedImagePtr();
   jpeg.length = jpeg_encoder.getCompressedImageSize();
 
-  JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, hdr_ratio, dest));
+  JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest));
 
   return NO_ERROR;
 }
@@ -131,6 +155,7 @@
 status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                                   jr_uncompressed_ptr uncompressed_yuv_420_image,
                                   jr_compressed_ptr compressed_jpeg_image,
+                                  jpegr_transfer_function hdr_tf,
                                   jr_compressed_ptr dest) {
   if (uncompressed_p010_image == nullptr
    || uncompressed_yuv_420_image == nullptr
@@ -144,26 +169,33 @@
     return ERROR_JPEGR_RESOLUTION_MISMATCH;
   }
 
+  jpegr_metadata metadata;
+  metadata.version = kJpegrVersion;
+  metadata.transferFunction = hdr_tf;
+  if (hdr_tf == JPEGR_TF_PQ) {
+    metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
+  }
+
   jpegr_uncompressed_struct map;
-  float hdr_ratio = 0.0f;
   JPEGR_CHECK(generateRecoveryMap(
-      uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio));
+      uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(map.data));
 
   jpegr_compressed_struct compressed_map;
-  std::unique_ptr<uint8_t[]> compressed_map_data =
-      std::make_unique<uint8_t[]>(map.width * map.height);
+  compressed_map.maxLength = map.width * map.height;
+  unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
   compressed_map.data = compressed_map_data.get();
   JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
 
-  JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, hdr_ratio, dest));
+  JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
 
   return NO_ERROR;
 }
 
 status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
                                   jr_compressed_ptr compressed_jpeg_image,
+                                  jpegr_transfer_function hdr_tf,
                                   jr_compressed_ptr dest) {
   if (uncompressed_p010_image == nullptr
    || compressed_jpeg_image == nullptr
@@ -179,39 +211,71 @@
   uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
   uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
   uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
+  uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
 
   if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
    || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
     return ERROR_JPEGR_RESOLUTION_MISMATCH;
   }
 
+  jpegr_metadata metadata;
+  metadata.version = kJpegrVersion;
+  metadata.transferFunction = hdr_tf;
+  if (hdr_tf == JPEGR_TF_PQ) {
+    metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
+  }
+
   jpegr_uncompressed_struct map;
-  float hdr_ratio = 0.0f;
   JPEGR_CHECK(generateRecoveryMap(
-      &uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio));
+      &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(map.data));
 
   jpegr_compressed_struct compressed_map;
-  std::unique_ptr<uint8_t[]> compressed_map_data =
-      std::make_unique<uint8_t[]>(map.width * map.height);
+  compressed_map.maxLength = map.width * map.height;
+  unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
   compressed_map.data = compressed_map_data.get();
   JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
 
-  JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, hdr_ratio, dest));
+  JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
 
   return NO_ERROR;
 }
 
+status_t RecoveryMap::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
+                                   jr_info_ptr jpegr_info) {
+  if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) {
+    return ERROR_JPEGR_INVALID_NULL_PTR;
+  }
+
+  jpegr_compressed_struct primary_image, recovery_map;
+  JPEGR_CHECK(extractPrimaryImageAndRecoveryMap(compressed_jpegr_image,
+                                                &primary_image, &recovery_map));
+
+  JpegDecoder jpeg_decoder;
+  if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
+                                                 &jpegr_info->width, &jpegr_info->height,
+                                                 jpegr_info->iccData, jpegr_info->exifData)) {
+    return ERROR_JPEGR_DECODE_ERROR;
+  }
+
+  return NO_ERROR;
+}
+
+
 status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
                                   jr_uncompressed_ptr dest,
-                                  jr_exif_ptr /* exif */,
-                                  bool /* request_sdr */) {
+                                  jr_exif_ptr exif,
+                                  bool request_sdr) {
   if (compressed_jpegr_image == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
+  // TODO: fill EXIF data
+  (void) exif;
+
   jpegr_compressed_struct compressed_map;
+  jpegr_metadata metadata;
   JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
 
   jpegr_uncompressed_struct map;
@@ -227,7 +291,19 @@
   uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
   uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
 
-  JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, dest));
+  if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()),
+                                       jpeg_decoder.getXMPSize(), &metadata)) {
+    return ERROR_JPEGR_DECODE_ERROR;
+  }
+
+  if (request_sdr) {
+    memcpy(dest->data, uncompressed_yuv_420_image.data,
+            uncompressed_yuv_420_image.width*uncompressed_yuv_420_image.height *3 / 2);
+    dest->width = uncompressed_yuv_420_image.width;
+    dest->height = uncompressed_yuv_420_image.height;
+  } else {
+    JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
+  }
 
   return NO_ERROR;
 }
@@ -257,30 +333,36 @@
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  // TODO: should we have ICC data?
+  // TODO: should we have ICC data for the map?
   JpegEncoder jpeg_encoder;
-  if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data, uncompressed_recovery_map->width,
-                                  uncompressed_recovery_map->height, 85, nullptr, 0,
+  if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data,
+                                  uncompressed_recovery_map->width,
+                                  uncompressed_recovery_map->height,
+                                  kMapCompressQuality,
+                                  nullptr,
+                                  0,
                                   true /* isSingleChannel */)) {
     return ERROR_JPEGR_ENCODE_ERROR;
   }
 
-  if (dest->length < jpeg_encoder.getCompressedImageSize()) {
+  if (dest->maxLength < jpeg_encoder.getCompressedImageSize()) {
     return ERROR_JPEGR_BUFFER_TOO_SMALL;
   }
 
   memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
   dest->length = jpeg_encoder.getCompressedImageSize();
+  dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
 
   return NO_ERROR;
 }
 
 status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
                                           jr_uncompressed_ptr uncompressed_p010_image,
-                                          jr_uncompressed_ptr dest,
-                                          float &hdr_ratio) {
+                                          jr_metadata_ptr metadata,
+                                          jr_uncompressed_ptr dest) {
   if (uncompressed_yuv_420_image == nullptr
    || uncompressed_p010_image == nullptr
+   || metadata == nullptr
    || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
@@ -290,6 +372,11 @@
     return ERROR_JPEGR_RESOLUTION_MISMATCH;
   }
 
+  if (uncompressed_yuv_420_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED
+   || uncompressed_p010_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED) {
+    return ERROR_JPEGR_INVALID_COLORGAMUT;
+  }
+
   size_t image_width = uncompressed_yuv_420_image->width;
   size_t image_height = uncompressed_yuv_420_image->height;
   size_t map_width = image_width / kMapDimensionScaleFactor;
@@ -297,25 +384,63 @@
 
   dest->width = map_width;
   dest->height = map_height;
+  dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
   dest->data = new uint8_t[map_width * map_height];
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
 
+  ColorTransformFn hdrInvOetf = nullptr;
+  switch (metadata->transferFunction) {
+    case JPEGR_TF_HLG:
+      hdrInvOetf = hlgInvOetf;
+      break;
+    case JPEGR_TF_PQ:
+      hdrInvOetf = pqInvOetf;
+      break;
+  }
+
+  ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
+      uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
+
+  ColorCalculationFn luminanceFn = nullptr;
+  switch (uncompressed_yuv_420_image->colorGamut) {
+    case JPEGR_COLORGAMUT_BT709:
+      luminanceFn = srgbLuminance;
+      break;
+    case JPEGR_COLORGAMUT_P3:
+      luminanceFn = p3Luminance;
+      break;
+    case JPEGR_COLORGAMUT_BT2100:
+      luminanceFn = bt2100Luminance;
+      break;
+    case JPEGR_COLORGAMUT_UNSPECIFIED:
+      // Should be impossible to hit after input validation.
+      return ERROR_JPEGR_INVALID_COLORGAMUT;
+  }
+
   float hdr_y_nits_max = 0.0f;
+  double hdr_y_nits_avg = 0.0f;
   for (size_t y = 0; y < image_height; ++y) {
     for (size_t x = 0; x < image_width; ++x) {
       Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
       Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
-      Color hdr_rgb = hlgInvOetf(hdr_rgb_gamma);
-      float hdr_y_nits = bt2100Luminance(hdr_rgb);
+      Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
+      hdr_rgb = hdrGamutConversionFn(hdr_rgb);
+      float hdr_y_nits = luminanceFn(hdr_rgb);
 
+      hdr_y_nits_avg += hdr_y_nits;
       if (hdr_y_nits > hdr_y_nits_max) {
         hdr_y_nits_max = hdr_y_nits;
       }
     }
   }
+  hdr_y_nits_avg /= image_width * image_height;
 
-  hdr_ratio = hdr_y_nits_max / kSdrWhiteNits;
+  metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits;
+  if (metadata->transferFunction == JPEGR_TF_PQ) {
+    metadata->hdr10Metadata.maxFALL = hdr_y_nits_avg;
+    metadata->hdr10Metadata.maxCLL = hdr_y_nits_max;
+  }
 
   for (size_t y = 0; y < map_height; ++y) {
     for (size_t x = 0; x < map_width; ++x) {
@@ -323,16 +448,17 @@
                                          kMapDimensionScaleFactor, x, y);
       Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
       Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
-      float sdr_y_nits = srgbLuminance(sdr_rgb);
+      float sdr_y_nits = luminanceFn(sdr_rgb);
 
       Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
       Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
-      Color hdr_rgb = hlgInvOetf(hdr_rgb_gamma);
-      float hdr_y_nits = bt2100Luminance(hdr_rgb);
+      Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
+      hdr_rgb = hdrGamutConversionFn(hdr_rgb);
+      float hdr_y_nits = luminanceFn(hdr_rgb);
 
       size_t pixel_idx =  x + y * map_width;
       reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
-          encodeRecovery(sdr_y_nits, hdr_y_nits, hdr_ratio);
+          encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
     }
   }
 
@@ -342,17 +468,15 @@
 
 status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
                                        jr_uncompressed_ptr uncompressed_recovery_map,
+                                       jr_metadata_ptr metadata,
                                        jr_uncompressed_ptr dest) {
   if (uncompressed_yuv_420_image == nullptr
    || uncompressed_recovery_map == nullptr
+   || metadata == nullptr
    || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  // TODO: need to get this from the XMP; should probably be a function
-  // parameter
-  float hdr_ratio = 4.0f;
-
   size_t width = uncompressed_yuv_420_image->width;
   size_t height = uncompressed_yuv_420_image->height;
 
@@ -360,61 +484,117 @@
   dest->height = height;
   size_t pixel_count = width * height;
 
+  ColorTransformFn hdrOetf = nullptr;
+  switch (metadata->transferFunction) {
+    case JPEGR_TF_HLG:
+      hdrOetf = hlgOetf;
+      break;
+    case JPEGR_TF_PQ:
+      hdrOetf = pqOetf;
+      break;
+  }
+
   for (size_t y = 0; y < height; ++y) {
     for (size_t x = 0; x < width; ++x) {
-      size_t pixel_y_idx =  x + y * width;
+      Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
+      Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
+      Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
 
-      size_t pixel_uv_idx = x / 2 + (y / 2) * (width / 2);
-
-      Color ypuv_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
-      Color rgbp_sdr = srgbYuvToRgb(ypuv_sdr);
-      Color rgb_sdr = srgbInvOetf(rgbp_sdr);
-
+      // TODO: determine map scaling factor based on actual map dims
       float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y);
-      Color rgb_hdr = applyRecovery(rgb_sdr, recovery, hdr_ratio);
+      Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata->rangeScalingFactor);
 
-      Color rgbp_hdr = hlgOetf(rgb_hdr);
-      // TODO: actually just leave in RGB and convert to RGBA1010102 instead.
-      Color ypuv_hdr = srgbRgbToYuv(rgbp_hdr);
+      Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
+      uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
 
-      reinterpret_cast<uint16_t*>(dest->data)[pixel_y_idx] = ypuv_hdr.r;
-      reinterpret_cast<uint16_t*>(dest->data)[pixel_count + pixel_uv_idx] = ypuv_hdr.g;
-      reinterpret_cast<uint16_t*>(dest->data)[pixel_count + pixel_uv_idx + 1] = ypuv_hdr.b;
+      size_t pixel_idx =  x + y * width;
+      reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
     }
   }
+  return NO_ERROR;
+}
+
+status_t RecoveryMap::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
+                                               jr_compressed_ptr primary_image,
+                                               jr_compressed_ptr recovery_map) {
+  if (compressed_jpegr_image == nullptr) {
+    return ERROR_JPEGR_INVALID_NULL_PTR;
+  }
+
+  MessageHandler msg_handler;
+  std::shared_ptr<DataSegment> seg =
+                  DataSegment::Create(DataRange(0, compressed_jpegr_image->length),
+                                      static_cast<const uint8_t*>(compressed_jpegr_image->data),
+                                      DataSegment::BufferDispositionPolicy::kDontDelete);
+  DataSegmentDataSource data_source(seg);
+  JpegInfoBuilder jpeg_info_builder;
+  jpeg_info_builder.SetImageLimit(2);
+  JpegScanner jpeg_scanner(&msg_handler);
+  jpeg_scanner.Run(&data_source, &jpeg_info_builder);
+  data_source.Reset();
+
+  if (jpeg_scanner.HasError()) {
+    return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  }
+
+  const auto& jpeg_info = jpeg_info_builder.GetInfo();
+  const auto& image_ranges = jpeg_info.GetImageRanges();
+  if (image_ranges.empty()) {
+    return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  }
+
+  if (image_ranges.size() != 2) {
+    // Must be 2 JPEG Images
+    return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  }
+
+  if (primary_image != nullptr) {
+    primary_image->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
+                                               image_ranges[0].GetBegin();
+    primary_image->length = image_ranges[0].GetLength();
+  }
+
+  if (recovery_map != nullptr) {
+    recovery_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
+                                              image_ranges[1].GetBegin();
+    recovery_map->length = image_ranges[1].GetLength();
+  }
 
   return NO_ERROR;
 }
 
+
 status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
                                          jr_compressed_ptr dest) {
   if (compressed_jpegr_image == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  // TBD
-  return NO_ERROR;
+  return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest);
 }
 
 status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
                                         jr_compressed_ptr compressed_recovery_map,
-                                        float hdr_ratio,
+                                        jr_metadata_ptr metadata,
                                         jr_compressed_ptr dest) {
   if (compressed_jpeg_image == nullptr
    || compressed_recovery_map == nullptr
+   || metadata == nullptr
    || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  string xmp = generateXmp(compressed_recovery_map->length, hdr_ratio);
-  string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
+  const string xmp = generateXmp(compressed_recovery_map->length, *metadata);
+  const string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
+  const int nameSpaceLength = nameSpace.size() + 1;  // need to count the null terminator
 
   // 2 bytes: APP1 sign (ff e1)
-  // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0"
+  // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0",
   // x bytes: length of xmp packet
-  int length = 2 + nameSpace.size() + xmp.size();
-  uint8_t lengthH = ((length >> 8) & 0xff);
-  uint8_t lengthL = (length & 0xff);
+
+  const int length = 3 + nameSpaceLength + xmp.size();
+  const uint8_t lengthH = ((length >> 8) & 0xff);
+  const uint8_t lengthL = (length & 0xff);
 
   int pos = 0;
 
@@ -432,7 +612,7 @@
   JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
   JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
   JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
-  JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpace.size(), pos));
+  JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
   JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
   JPEGR_CHECK(Write(dest,
       (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
@@ -442,7 +622,7 @@
   return NO_ERROR;
 }
 
-string RecoveryMap::generateXmp(int secondary_image_length, float hdr_ratio) {
+string RecoveryMap::generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
   const string kContainerPrefix = "GContainer";
   const string kContainerUri    = "http://ns.google.com/photos/1.0/container/";
   const string kItemPrefix      = "Item";
@@ -455,7 +635,6 @@
   const string kPrimary         = "Primary";
   const string kSemantic        = "Semantic";
   const string kVersion         = "Version";
-  const int    kVersionValue    = 1;
 
   const string kConDir          = Name(kContainerPrefix, kDirectory);
   const string kContainerItem   = Name(kContainerPrefix, kItem);
@@ -475,8 +654,11 @@
   writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
   writer.StartWritingElement("rdf:Description");
   writer.WriteXmlns(kContainerPrefix, kContainerUri);
-  writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), kVersionValue);
-  writer.WriteElementAndContent(Name(kContainerPrefix, "HdrRatio"), hdr_ratio);
+  writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), metadata.version);
+  writer.WriteElementAndContent(Name(kContainerPrefix, "rangeScalingFactor"),
+                                metadata.rangeScalingFactor);
+  // TODO: determine structure for hdr10 metadata
+  // TODO: write rest of metadata
   writer.StartWritingElements(kConDirSeq);
   size_t item_depth = writer.StartWritingElements(kLiItem);
   writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary);
diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp
index 0d3319f..6dcbca3 100644
--- a/libs/jpegrecoverymap/recoverymapmath.cpp
+++ b/libs/jpegrecoverymap/recoverymapmath.cpp
@@ -64,6 +64,11 @@
 ////////////////////////////////////////////////////////////////////////////////
 // Display-P3 transformations
 
+static const float kP3R = 0.22897f, kP3G = 0.69174f, kP3B = 0.07929f;
+
+float p3Luminance(Color e) {
+  return kP3R * e.r + kP3G * e.g + kP3B * e.b;
+}
 
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -128,7 +133,7 @@
   return {{{ hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b) }}};
 }
 
-float hlgInvOetf(float e_gamma) {
+static float hlgInvOetf(float e_gamma) {
   if (e_gamma <= 0.5f) {
     return pow(e_gamma, 2.0f) / 3.0f;
   } else {
@@ -142,10 +147,118 @@
              hlgInvOetf(e_gamma.b) }}};
 }
 
+static const float kPqM1 = 2610.0f / 16384.0f, kPqM2 = 2523.0f / 4096.0f * 128.0f;
+static const float kPqC1 = 3424.0f / 4096.0f, kPqC2 = 2413.0f / 4096.0f * 32.0f,
+                   kPqC3 = 2392.0f / 4096.0f * 32.0f;
+
+static float pqOetf(float e) {
+  if (e < 0.0f) e = 0.0f;
+  return pow((kPqC1 + kPqC2 * pow(e / 10000.0f, kPqM1)) / (1 + kPqC3 * pow(e / 10000.0f, kPqM1)),
+             kPqM2);
+}
+
+Color pqOetf(Color e) {
+  return {{{ pqOetf(e.r), pqOetf(e.g), pqOetf(e.b) }}};
+}
+
+static float pqInvOetf(float e_gamma) {
+  static const float kPqInvOetfCoef = log2(-(pow(kPqM1, 1.0f / kPqM2) - kPqC1)
+                                         / (kPqC3 * pow(kPqM1, 1.0f / kPqM2) - kPqC2));
+  return kPqInvOetfCoef / log2(e_gamma * 10000.0f);
+}
+
+Color pqInvOetf(Color e_gamma) {
+  return {{{ pqInvOetf(e_gamma.r),
+             pqInvOetf(e_gamma.g),
+             pqInvOetf(e_gamma.b) }}};
+}
+
 
 ////////////////////////////////////////////////////////////////////////////////
 // Color conversions
 
+Color bt709ToP3(Color e) {
+ return {{{ 0.82254f * e.r + 0.17755f * e.g + 0.00006f * e.b,
+            0.03312f * e.r + 0.96684f * e.g + -0.00001f * e.b,
+            0.01706f * e.r + 0.07240f * e.g + 0.91049f * e.b }}};
+}
+
+Color bt709ToBt2100(Color e) {
+ return {{{ 0.62740f * e.r + 0.32930f * e.g + 0.04332f * e.b,
+            0.06904f * e.r + 0.91958f * e.g + 0.01138f * e.b,
+            0.01636f * e.r + 0.08799f * e.g + 0.89555f * e.b }}};
+}
+
+Color p3ToBt709(Color e) {
+ return {{{ 1.22482f * e.r + -0.22490f * e.g + -0.00007f * e.b,
+            -0.04196f * e.r + 1.04199f * e.g + 0.00001f * e.b,
+            -0.01961f * e.r + -0.07865f * e.g + 1.09831f * e.b }}};
+}
+
+Color p3ToBt2100(Color e) {
+ return {{{ 0.75378f * e.r + 0.19862f * e.g + 0.04754f * e.b,
+            0.04576f * e.r + 0.94177f * e.g + 0.01250f * e.b,
+            -0.00121f * e.r + 0.01757f * e.g + 0.98359f * e.b }}};
+}
+
+Color bt2100ToBt709(Color e) {
+ return {{{ 1.66045f * e.r + -0.58764f * e.g + -0.07286f * e.b,
+            -0.12445f * e.r + 1.13282f * e.g + -0.00837f * e.b,
+            -0.01811f * e.r + -0.10057f * e.g + 1.11878f * e.b }}};
+}
+
+Color bt2100ToP3(Color e) {
+ return {{{ 1.34369f * e.r + -0.28223f * e.g + -0.06135f * e.b,
+            -0.06533f * e.r + 1.07580f * e.g + -0.01051f * e.b,
+            0.00283f * e.r + -0.01957f * e.g + 1.01679f * e.b
+ }}};
+}
+
+// TODO: confirm we always want to convert like this before calculating
+// luminance.
+ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gamut hdr_gamut) {
+    switch (sdr_gamut) {
+    case JPEGR_COLORGAMUT_BT709:
+      switch (hdr_gamut) {
+        case JPEGR_COLORGAMUT_BT709:
+          return identityConversion;
+        case JPEGR_COLORGAMUT_P3:
+          return p3ToBt709;
+        case JPEGR_COLORGAMUT_BT2100:
+          return bt2100ToBt709;
+        case JPEGR_COLORGAMUT_UNSPECIFIED:
+          return nullptr;
+      }
+      break;
+    case JPEGR_COLORGAMUT_P3:
+      switch (hdr_gamut) {
+        case JPEGR_COLORGAMUT_BT709:
+          return bt709ToP3;
+        case JPEGR_COLORGAMUT_P3:
+          return identityConversion;
+        case JPEGR_COLORGAMUT_BT2100:
+          return bt2100ToP3;
+        case JPEGR_COLORGAMUT_UNSPECIFIED:
+          return nullptr;
+      }
+      break;
+    case JPEGR_COLORGAMUT_BT2100:
+      switch (hdr_gamut) {
+        case JPEGR_COLORGAMUT_BT709:
+          return bt709ToBt2100;
+        case JPEGR_COLORGAMUT_P3:
+          return p3ToBt2100;
+        case JPEGR_COLORGAMUT_BT2100:
+          return identityConversion;
+        case JPEGR_COLORGAMUT_UNSPECIFIED:
+          return nullptr;
+      }
+      break;
+    case JPEGR_COLORGAMUT_UNSPECIFIED:
+      return nullptr;
+  }
+}
+
 
 ////////////////////////////////////////////////////////////////////////////////
 // Recovery map calculations
@@ -175,6 +288,10 @@
 // TODO: do we need something more clever for filtering either the map or images
 // to generate the map?
 
+static size_t clamp(const size_t& val, const size_t& low, const size_t& high) {
+  return val < low ? low : (high < val ? high : val);
+}
+
 static float mapUintToFloat(uint8_t map_uint) {
   return (static_cast<float>(map_uint) - 127.5f) / 127.5f;
 }
@@ -188,6 +305,11 @@
   size_t y_lower = static_cast<size_t>(floor(y_map));
   size_t y_upper = y_lower + 1;
 
+  x_lower = clamp(x_lower, 0, map->width - 1);
+  x_upper = clamp(x_upper, 0, map->width - 1);
+  y_lower = clamp(y_lower, 0, map->height - 1);
+  y_upper = clamp(y_upper, 0, map->height - 1);
+
   float x_influence = x_map - static_cast<float>(x_lower);
   float y_influence = y_map - static_cast<float>(y_lower);
 
@@ -225,9 +347,12 @@
   size_t pixel_y_idx = x + y * image->width;
   size_t pixel_uv_idx = x / 2 + (y / 2) * (image->width / 2);
 
-  uint16_t y_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_y_idx];
-  uint16_t u_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_count + pixel_uv_idx * 2];
-  uint16_t v_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_count + pixel_uv_idx * 2 + 1];
+  uint16_t y_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_y_idx]
+                  >> 6;
+  uint16_t u_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_count + pixel_uv_idx * 2]
+                  >> 6;
+  uint16_t v_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_count + pixel_uv_idx * 2 + 1]
+                  >> 6;
 
   // Conversions include taking narrow-range into account.
   return {{{ static_cast<float>(y_uint) / 940.0f,
@@ -257,4 +382,11 @@
   return samplePixels(image, map_scale_factor, x, y, getP010Pixel);
 }
 
+uint32_t colorToRgba1010102(Color e_gamma) {
+  return (0x3ff & static_cast<uint32_t>(e_gamma.r * 1023.0f))
+       | ((0x3ff & static_cast<uint32_t>(e_gamma.g * 1023.0f)) << 10)
+       | ((0x3ff & static_cast<uint32_t>(e_gamma.b * 1023.0f)) << 20)
+       | (0x3 << 30);  // Set alpha to 1.0
+}
+
 } // namespace android::recoverymap
diff --git a/libs/jpegrecoverymap/recoverymaputils.cpp b/libs/jpegrecoverymap/recoverymaputils.cpp
new file mode 100644
index 0000000..fe46cba
--- /dev/null
+++ b/libs/jpegrecoverymap/recoverymaputils.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jpegrecoverymap/recoverymaputils.h>
+#include <jpegrecoverymap/recoverymap.h>
+#include <image_io/xml/xml_reader.h>
+#include <image_io/base/message_handler.h>
+#include <image_io/xml/xml_element_rules.h>
+#include <image_io/xml/xml_handler.h>
+#include <image_io/xml/xml_rule.h>
+
+#include <string>
+#include <sstream>
+
+using namespace photos_editing_formats::image_io;
+using namespace std;
+
+namespace android::recoverymap {
+
+
+// Extremely simple XML Handler - just searches for interesting elements
+class XMPXmlHandler : public XmlHandler {
+public:
+
+    XMPXmlHandler() : XmlHandler() {
+        rangeScalingFactorState = NotStrarted;
+    }
+
+    enum ParseState {
+        NotStrarted,
+        Started,
+        Done
+    };
+
+    virtual DataMatchResult StartElement(const XmlTokenContext& context) {
+        string val;
+        if (context.BuildTokenValue(&val)) {
+            if (!val.compare(rangeScalingFactorName)) {
+                rangeScalingFactorState = Started;
+            } else {
+                if (rangeScalingFactorState != Done) {
+                    rangeScalingFactorState = NotStrarted;
+                }
+            }
+        }
+        return context.GetResult();
+    }
+
+    virtual DataMatchResult FinishElement(const XmlTokenContext& context) {
+        if (rangeScalingFactorState == Started) {
+            rangeScalingFactorState = Done;
+        }
+        return context.GetResult();
+    }
+
+    virtual DataMatchResult ElementContent(const XmlTokenContext& context) {
+        string val;
+        if (rangeScalingFactorState == Started) {
+            if (context.BuildTokenValue(&val)) {
+                rangeScalingFactorStr.assign(val);
+            }
+        }
+        return context.GetResult();
+    }
+
+    bool getRangeScalingFactor(float* scaling_factor) {
+        if (rangeScalingFactorState == Done) {
+            stringstream ss(rangeScalingFactorStr);
+            float val;
+            if (ss >> val) {
+                *scaling_factor = val;
+                return true;
+            } else {
+                return false;
+            }
+        } else {
+            return false;
+        }
+    }
+
+    bool getTransferFunction(jpegr_transfer_function* transfer_function) {
+        *transfer_function = JPEGR_TF_HLG;
+        return true;
+    }
+
+private:
+    static const string rangeScalingFactorName;
+    string              rangeScalingFactorStr;
+    ParseState          rangeScalingFactorState;
+};
+
+const string XMPXmlHandler::rangeScalingFactorName = "GContainer:rangeScalingFactor";
+
+
+bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) {
+    string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
+
+    if (xmp_size < nameSpace.size()+2) {
+        // Data too short
+        return false;
+    }
+
+    if (strncmp(reinterpret_cast<char*>(xmp_data), nameSpace.c_str(), nameSpace.size())) {
+        // Not correct namespace
+        return false;
+    }
+
+    // Position the pointers to the start of XMP XML portion
+    xmp_data += nameSpace.size()+1;
+    xmp_size -= nameSpace.size()+1;
+    XMPXmlHandler handler;
+
+    // We need to remove tail data until the closing tag. Otherwise parser will throw an error.
+    while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) {
+        xmp_size--;
+    }
+
+    string str(reinterpret_cast<const char*>(xmp_data), xmp_size);
+    MessageHandler msg_handler;
+    unique_ptr<XmlRule> rule(new XmlElementRule);
+    XmlReader reader(&handler, &msg_handler);
+    reader.StartParse(std::move(rule));
+    reader.Parse(str);
+    reader.FinishParse();
+    if (reader.HasErrors()) {
+        // Parse error
+        return false;
+    }
+
+    if (!handler.getRangeScalingFactor(&metadata->rangeScalingFactor)) {
+        return false;
+    }
+
+    if (!handler.getTransferFunction(&metadata->transferFunction)) {
+        return false;
+    }
+    return true;
+}
+
+} // namespace android::recoverymap
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
index 226056b..b3cd37e 100644
--- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
@@ -37,9 +37,11 @@
 TEST_F(RecoveryMapTest, build) {
   // Force all of the recovery map lib to be linked by calling all public functions.
   RecoveryMap recovery_map;
-  recovery_map.encodeJPEGR(nullptr, nullptr, nullptr, 0, nullptr);
-  recovery_map.encodeJPEGR(nullptr, nullptr, nullptr, nullptr);
-  recovery_map.encodeJPEGR(nullptr, nullptr, nullptr);
+  recovery_map.encodeJPEGR(nullptr, nullptr, static_cast<jpegr_transfer_function>(0),
+                           nullptr, 0, nullptr);
+  recovery_map.encodeJPEGR(nullptr, nullptr, nullptr, static_cast<jpegr_transfer_function>(0),
+                           nullptr);
+  recovery_map.encodeJPEGR(nullptr, nullptr, static_cast<jpegr_transfer_function>(0), nullptr);
   recovery_map.decodeJPEGR(nullptr, nullptr, nullptr, false);
 }
 
diff --git a/libs/sensor/Android.bp b/libs/sensor/Android.bp
index 2b93c6e..b6b9cc4 100644
--- a/libs/sensor/Android.bp
+++ b/libs/sensor/Android.bp
@@ -21,9 +21,10 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
-cc_library_shared {
+cc_library {
     name: "libsensor",
 
+    host_supported: true,
     cflags: [
         "-Wall",
         "-Werror",
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index d1de551..b885435 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -60,7 +60,6 @@
     name: "libinputflinger_sources",
     srcs: [
         "InputCommonConverter.cpp",
-        "InputManager.cpp",
         "InputProcessor.cpp",
         "PreferStylusOverTouchBlocker.cpp",
         "UnwantedInteractionBlocker.cpp",
@@ -116,6 +115,10 @@
         "inputflinger_defaults",
         "libinputflinger_defaults",
     ],
+    srcs: [
+        "InputManager.cpp",
+        // other sources are added via "defaults"
+    ],
     cflags: [
         // TODO(b/23084678): Move inputflinger to its own process and mark it hidden
         //-fvisibility=hidden
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 4a0f2ec..b3b4fb1 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -41,6 +41,7 @@
           "include-filter": "android.view.cts.input",
           "include-filter": "android.view.cts.MotionEventTest",
           "include-filter": "android.view.cts.PointerCaptureTest",
+          "include-filter": "android.view.cts.TooltipTest",
           "include-filter": "android.view.cts.VerifyInputEventTest"
         }
       ]
@@ -128,6 +129,7 @@
         {
           "include-filter": "android.view.cts.MotionEventTest",
           "include-filter": "android.view.cts.PointerCaptureTest",
+          "include-filter": "android.view.cts.TooltipTest",
           "include-filter": "android.view.cts.VerifyInputEventTest"
         }
       ]
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 87a4ff4..6d30090 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -1769,15 +1769,16 @@
 
 void InputDispatcher::logOutboundMotionDetails(const char* prefix, const MotionEntry& entry) {
     if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-        ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%" PRId32
+        ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=%s, displayId=%" PRId32
               ", policyFlags=0x%x, "
               "action=%s, actionButton=0x%x, flags=0x%x, "
               "metaState=0x%x, buttonState=0x%x,"
               "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%" PRId64,
-              prefix, entry.eventTime, entry.deviceId, entry.source, entry.displayId,
-              entry.policyFlags, MotionEvent::actionToString(entry.action).c_str(),
-              entry.actionButton, entry.flags, entry.metaState, entry.buttonState, entry.edgeFlags,
-              entry.xPrecision, entry.yPrecision, entry.downTime);
+              prefix, entry.eventTime, entry.deviceId,
+              inputEventSourceToString(entry.source).c_str(), entry.displayId, entry.policyFlags,
+              MotionEvent::actionToString(entry.action).c_str(), entry.actionButton, entry.flags,
+              entry.metaState, entry.buttonState, entry.edgeFlags, entry.xPrecision,
+              entry.yPrecision, entry.downTime);
 
         for (uint32_t i = 0; i < entry.pointerCount; i++) {
             ALOGD("  Pointer %d: id=%d, toolType=%d, "
@@ -2305,6 +2306,20 @@
                                                  entry.eventTime);
             }
         }
+
+        // Update the pointerIds for non-splittable when it received pointer down.
+        if (!isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
+            // If no split, we suppose all touched windows should receive pointer down.
+            const int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+            for (size_t i = 0; i < tempTouchState.windows.size(); i++) {
+                TouchedWindow& touchedWindow = tempTouchState.windows[i];
+                // Ignore drag window for it should just track one pointer.
+                if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) {
+                    continue;
+                }
+                touchedWindow.pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
+            }
+        }
     }
 
     // Update dispatching for hover enter and exit.
@@ -2479,17 +2494,6 @@
             }
             i += 1;
         }
-    } else if (!isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
-        // If no split, we suppose all touched windows should receive pointer down.
-        const int32_t pointerIndex = getMotionEventActionPointerIndex(action);
-        for (size_t i = 0; i < tempTouchState.windows.size(); i++) {
-            TouchedWindow& touchedWindow = tempTouchState.windows[i];
-            // Ignore drag window for it should just track one pointer.
-            if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) {
-                continue;
-            }
-            touchedWindow.pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
-        }
     }
 
     // Save changes unless the action was scroll in which case the temporary touch
@@ -2502,6 +2506,10 @@
         }
     }
 
+    if (tempTouchState.windows.empty()) {
+        mTouchStatesByDisplay.erase(displayId);
+    }
+
     // Update hover state.
     mLastHoverWindowHandle = newHoverWindowHandle;
 
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index ee7da93..114e0bf 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -148,7 +148,8 @@
 
 std::string TouchState::dump() const {
     std::string out;
-    out += StringPrintf("deviceId=%d, source=0x%08x\n", deviceId, source);
+    out += StringPrintf("deviceId=%d, source=%s\n", deviceId,
+                        inputEventSourceToString(source).c_str());
     if (!windows.empty()) {
         out += "  Windows:\n";
         for (size_t i = 0; i < windows.size(); i++) {
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index ccff353..b193dff 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -119,6 +119,18 @@
             if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
                 outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
             }
+        } else if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS && !mStylusMtToolSeen) {
+            mStylusMtToolSeen = true;
+            // The multi-touch device produced a stylus event with MT_TOOL_PEN. Dynamically
+            // re-configure this input device so that we add SOURCE_STYLUS if we haven't already.
+            // This is to cover the case where we cannot reliably detect whether a multi-touch
+            // device will ever produce stylus events when it is initially being configured.
+            if (!isFromSource(mSource, AINPUT_SOURCE_STYLUS)) {
+                // Add the stylus source immediately so that it is included in any events generated
+                // before we have a chance to re-configure the device.
+                mSource |= AINPUT_SOURCE_STYLUS;
+                bumpGeneration();
+            }
         }
         if (shouldSimulateStylusWithTouch() &&
             outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) {
@@ -200,7 +212,8 @@
 }
 
 bool MultiTouchInputMapper::hasStylus() const {
-    return mTouchButtonAccumulator.hasStylus() || shouldSimulateStylusWithTouch();
+    return mStylusMtToolSeen || mTouchButtonAccumulator.hasStylus() ||
+            shouldSimulateStylusWithTouch();
 }
 
 bool MultiTouchInputMapper::shouldSimulateStylusWithTouch() const {
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
index ddf9e80..5f8bccf 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
@@ -50,6 +50,8 @@
     // Specifies the pointer id bits that are in use, and their associated tracking id.
     BitSet32 mPointerIdBits;
     int32_t mPointerTrackingIdMap[MAX_POINTER_ID + 1];
+
+    bool mStylusMtToolSeen{false};
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
index 29a1bda..06d4dc3 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
@@ -97,35 +97,34 @@
 
 std::list<NotifyArgs> RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readTime) {
     std::list<NotifyArgs> out;
-    PointerCoords pointerCoords;
-    pointerCoords.clear();
-
-    PointerProperties pointerProperties;
-    pointerProperties.clear();
-    pointerProperties.id = 0;
-    pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
 
     float scroll = mRotaryEncoderScrollAccumulator.getRelativeVWheel();
     bool scrolled = scroll != 0;
 
-    // This is not a pointer, so it's not associated with a display.
-    int32_t displayId = ADISPLAY_ID_NONE;
-
-    // Moving the rotary encoder should wake the device (if specified).
-    uint32_t policyFlags = 0;
-    if (scrolled && getDeviceContext().isExternal()) {
-        policyFlags |= POLICY_FLAG_WAKE;
-    }
-
-    if (mOrientation == DISPLAY_ORIENTATION_180) {
-        scroll = -scroll;
-    }
-
     // Send motion event.
     if (scrolled) {
         int32_t metaState = getContext()->getGlobalMetaState();
+        // This is not a pointer, so it's not associated with a display.
+        int32_t displayId = ADISPLAY_ID_NONE;
+
+        if (mOrientation == DISPLAY_ORIENTATION_180) {
+            scroll = -scroll;
+        }
+
+        PointerCoords pointerCoords;
+        pointerCoords.clear();
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_SCROLL, scroll * mScalingFactor);
 
+        PointerProperties pointerProperties;
+        pointerProperties.clear();
+        pointerProperties.id = 0;
+        pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+
+        uint32_t policyFlags = 0;
+        if (getDeviceContext().isExternal()) {
+            policyFlags |= POLICY_FLAG_WAKE;
+        }
+
         out.push_back(
                 NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
                                  displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0,
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index bc55fe5..5be694f 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -3763,15 +3763,12 @@
     PointerCoords pointerCoords[MAX_POINTERS];
     PointerProperties pointerProperties[MAX_POINTERS];
     uint32_t pointerCount = 0;
-    bool stylusToolFound = false;
     while (!idBits.isEmpty()) {
         uint32_t id = idBits.clearFirstMarkedBit();
         uint32_t index = idToIndex[id];
         pointerProperties[pointerCount].copyFrom(properties[index]);
         pointerCoords[pointerCount].copyFrom(coords[index]);
 
-        stylusToolFound |= isStylusToolType(pointerProperties[pointerCount].toolType);
-
         if (changedId >= 0 && id == uint32_t(changedId)) {
             action |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
         }
@@ -3803,12 +3800,6 @@
     if (mDeviceMode == DeviceMode::POINTER) {
         mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
     }
-    if (stylusToolFound) {
-        // Dynamically add the stylus source when there's a stylus tool being used to cover the case
-        // where we cannot reliably detect whether a multi-touch device will ever produce stylus
-        // events when it is initially being configured.
-        source |= AINPUT_SOURCE_STYLUS;
-    }
     const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
     const int32_t deviceId = getDeviceId();
     std::vector<TouchVideoFrame> frames = getDeviceContext().getVideoFrames();
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index b6d0709..e55e121 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -81,9 +81,6 @@
         "libc++fs",
         "libgmock",
     ],
-    shared_libs: [
-        "libinputreader",
-    ],
     require_root: true,
     test_options: {
         unit_test: true,
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index aaf50ce..7817eca 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -22,6 +22,7 @@
 #include <android-base/thread_annotations.h>
 #include <binder/Binder.h>
 #include <fcntl.h>
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <input/Input.h>
 #include <linux/input.h>
@@ -43,6 +44,7 @@
 namespace android::inputdispatcher {
 
 using namespace ftl::flag_operators;
+using testing::AllOf;
 
 // An arbitrary time value.
 static constexpr nsecs_t ARBITRARY_TIME = 1234;
@@ -102,6 +104,28 @@
             << MotionEvent::actionToString(receivedAction);
 }
 
+MATCHER_P(WithMotionAction, action, "MotionEvent with specified action") {
+    bool matches = action == arg.getAction();
+    if (!matches) {
+        *result_listener << "expected action " << MotionEvent::actionToString(action)
+                         << ", but got " << MotionEvent::actionToString(arg.getAction());
+    }
+    if (action == AMOTION_EVENT_ACTION_CANCEL) {
+        if (!matches) {
+            *result_listener << "; ";
+        }
+        *result_listener << "expected FLAG_CANCELED to be set with ACTION_CANCEL, but was not set";
+        matches &= (arg.getFlags() & AMOTION_EVENT_FLAG_CANCELED) != 0;
+    }
+    return matches;
+}
+
+MATCHER_P(WithSource, source, "InputEvent with specified source") {
+    *result_listener << "expected source " << inputEventSourceToString(source) << ", but got "
+                     << inputEventSourceToString(arg.getSource());
+    return arg.getSource() == source;
+}
+
 // --- FakeInputDispatcherPolicy ---
 
 class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
@@ -1205,6 +1229,12 @@
         mInputReceiver->consumeCaptureEvent(hasCapture);
     }
 
+    void consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
+        MotionEvent* motionEvent = consumeMotion();
+        ASSERT_NE(nullptr, motionEvent) << "Did not get a motion event";
+        ASSERT_THAT(*motionEvent, matcher);
+    }
+
     void consumeEvent(int32_t expectedEventType, int32_t expectedAction,
                       std::optional<int32_t> expectedDisplayId,
                       std::optional<int32_t> expectedFlags) {
@@ -2207,6 +2237,48 @@
                          ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
 }
 
+/**
+ * Inject a mouse hover event followed by a tap from touchscreen.
+ * In the current implementation, the tap does not cause a HOVER_EXIT event.
+ */
+TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 100, 100));
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    // Inject a hover_move from mouse.
+    NotifyMotionArgs motionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE,
+                               ADISPLAY_ID_DEFAULT, {{50, 50}});
+    motionArgs.xCursorPosition = 50;
+    motionArgs.yCursorPosition = 50;
+    mDispatcher->notifyMotion(&motionArgs);
+    ASSERT_NO_FATAL_FAILURE(
+            window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                             WithSource(AINPUT_SOURCE_MOUSE))));
+    ASSERT_NO_FATAL_FAILURE(
+            window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                             WithSource(AINPUT_SOURCE_MOUSE))));
+
+    // Tap on the window
+    motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                    ADISPLAY_ID_DEFAULT, {{10, 10}});
+    mDispatcher->notifyMotion(&motionArgs);
+    ASSERT_NO_FATAL_FAILURE(
+            window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                             WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
+
+    motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                    ADISPLAY_ID_DEFAULT, {{10, 10}});
+    mDispatcher->notifyMotion(&motionArgs);
+    ASSERT_NO_FATAL_FAILURE(
+            window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                             WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
+}
+
 TEST_F(InputDispatcherTest, DispatchMouseEventsUnderCursor) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
@@ -2400,6 +2472,43 @@
     window->assertNoEvents();
 }
 
+TEST_F(InputDispatcherTest, NonSplitTouchableWindowReceivesMultiTouch) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    // Ensure window is non-split and have some transform.
+    window->setPreventSplitting(true);
+    window->setWindowOffset(20, 40);
+    mDispatcher->onWindowInfosChanged({*window->getInfo()}, {});
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {50, 50}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+    const MotionEvent secondFingerDownEvent =
+            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(-30)
+                                     .y(-50))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+                                InputEventInjectionSync::WAIT_FOR_RESULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    const MotionEvent* event = window->consumeMotion();
+    EXPECT_EQ(POINTER_1_DOWN, event->getAction());
+    EXPECT_EQ(70, event->getX(0));  // 50 + 20
+    EXPECT_EQ(90, event->getY(0));  // 50 + 40
+    EXPECT_EQ(-10, event->getX(1)); // -30 + 20
+    EXPECT_EQ(-10, event->getY(1)); // -50 + 40
+}
+
 /**
  * Ensure the correct coordinate spaces are used by InputDispatcher.
  *
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 5c5fc77..6b6d0bb 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -10788,11 +10788,12 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
 }
 
-TEST_F(MultiTouchInputMapperTest, ToolTypeSource) {
+TEST_F(MultiTouchInputMapperTest, StylusSourceIsAddedDynamicallyFromToolType) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(DISPLAY_ORIENTATION_0);
     prepareAxes(POSITION | ID | SLOT | PRESSURE | TOOL_TYPE);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
 
     // Even if the device supports reporting the ABS_MT_TOOL_TYPE axis, which could give it the
     // ability to report MT_TOOL_PEN, we do not report the device as coming from a stylus source.
@@ -10801,7 +10802,7 @@
     ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources());
 
     // However, if the device ever ends up reporting an event with MT_TOOL_PEN, it should be
-    // reported with the stylus source, even through the device doesn't support the stylus source.
+    // reported with the stylus source.
     processId(mapper, FIRST_TRACKING_ID);
     processToolType(mapper, MT_TOOL_PEN);
     processPosition(mapper, 100, 200);
@@ -10812,15 +10813,27 @@
                   WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
                   WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
 
+    // Now that we know the device supports styluses, ensure that the device is re-configured with
+    // the stylus source.
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS, mapper.getSources());
+    {
+        const auto& devices = mReader->getInputDevices();
+        auto deviceInfo =
+                std::find_if(devices.begin(), devices.end(),
+                             [](const InputDeviceInfo& info) { return info.getId() == DEVICE_ID; });
+        LOG_ALWAYS_FATAL_IF(deviceInfo == devices.end(), "Cannot find InputDevice");
+        ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS, deviceInfo->getSources());
+    }
+
+    // Ensure the device was not reset to prevent interruptions of any ongoing gestures.
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasNotCalled());
+
     processId(mapper, INVALID_TRACKING_ID);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                   WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
                   WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
-
-    // The mapper should still report only a touchscreen source.
-    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources());
 }
 
 // --- MultiTouchInputMapperTest_ExternalDevice ---
diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h
index 8721bd8..9db3422 100644
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ b/services/inputflinger/tests/TestInputListenerMatchers.h
@@ -46,7 +46,8 @@
 }
 
 MATCHER_P(WithSource, source, "InputEvent with specified source") {
-    *result_listener << "expected source " << source << ", but got " << arg.source;
+    *result_listener << "expected source " << inputEventSourceToString(source) << ", but got "
+                     << inputEventSourceToString(arg.source);
     return arg.source == source;
 }
 
diff --git a/services/sensorservice/AidlSensorHalWrapper.cpp b/services/sensorservice/AidlSensorHalWrapper.cpp
index f67c610..f5b360f 100644
--- a/services/sensorservice/AidlSensorHalWrapper.cpp
+++ b/services/sensorservice/AidlSensorHalWrapper.cpp
@@ -23,6 +23,7 @@
 #include <aidlcommonsupport/NativeHandle.h>
 #include <android-base/logging.h>
 #include <android/binder_manager.h>
+#include <aidl/sensors/convert.h>
 
 using ::aidl::android::hardware::sensors::AdditionalInfo;
 using ::aidl::android::hardware::sensors::DynamicSensorInfo;
@@ -34,469 +35,16 @@
 using ::android::AidlMessageQueue;
 using ::android::hardware::EventFlag;
 using ::android::hardware::sensors::V2_1::implementation::MAX_RECEIVE_BUFFER_EVENT_COUNT;
+using ::android::hardware::sensors::implementation::convertToStatus;
+using ::android::hardware::sensors::implementation::convertToSensor;
+using ::android::hardware::sensors::implementation::convertToSensorEvent;
+using ::android::hardware::sensors::implementation::convertFromSensorEvent;
+
 
 namespace android {
 
 namespace {
 
-status_t convertToStatus(ndk::ScopedAStatus status) {
-    if (status.isOk()) {
-        return OK;
-    } else {
-        switch (status.getExceptionCode()) {
-            case EX_ILLEGAL_ARGUMENT: {
-                return BAD_VALUE;
-            }
-            case EX_SECURITY: {
-                return PERMISSION_DENIED;
-            }
-            case EX_UNSUPPORTED_OPERATION: {
-                return INVALID_OPERATION;
-            }
-            case EX_SERVICE_SPECIFIC: {
-                switch (status.getServiceSpecificError()) {
-                    case ISensors::ERROR_BAD_VALUE: {
-                        return BAD_VALUE;
-                    }
-                    case ISensors::ERROR_NO_MEMORY: {
-                        return NO_MEMORY;
-                    }
-                    default: {
-                        return UNKNOWN_ERROR;
-                    }
-                }
-            }
-            default: {
-                return UNKNOWN_ERROR;
-            }
-        }
-    }
-}
-
-void convertToSensor(const SensorInfo &src, sensor_t *dst) {
-    dst->name = strdup(src.name.c_str());
-    dst->vendor = strdup(src.vendor.c_str());
-    dst->version = src.version;
-    dst->handle = src.sensorHandle;
-    dst->type = (int)src.type;
-    dst->maxRange = src.maxRange;
-    dst->resolution = src.resolution;
-    dst->power = src.power;
-    dst->minDelay = src.minDelayUs;
-    dst->fifoReservedEventCount = src.fifoReservedEventCount;
-    dst->fifoMaxEventCount = src.fifoMaxEventCount;
-    dst->stringType = strdup(src.typeAsString.c_str());
-    dst->requiredPermission = strdup(src.requiredPermission.c_str());
-    dst->maxDelay = src.maxDelayUs;
-    dst->flags = src.flags;
-    dst->reserved[0] = dst->reserved[1] = 0;
-}
-
-void convertToSensorEvent(const Event &src, sensors_event_t *dst) {
-    *dst = {.version = sizeof(sensors_event_t),
-            .sensor = src.sensorHandle,
-            .type = (int32_t)src.sensorType,
-            .reserved0 = 0,
-            .timestamp = src.timestamp};
-
-    switch (src.sensorType) {
-        case SensorType::META_DATA: {
-            // Legacy HALs expect the handle reference in the meta data field.
-            // Copy it over from the handle of the event.
-            dst->meta_data.what = (int32_t)src.payload.get<Event::EventPayload::meta>().what;
-            dst->meta_data.sensor = src.sensorHandle;
-            // Set the sensor handle to 0 to maintain compatibility.
-            dst->sensor = 0;
-            break;
-        }
-
-        case SensorType::ACCELEROMETER:
-        case SensorType::MAGNETIC_FIELD:
-        case SensorType::ORIENTATION:
-        case SensorType::GYROSCOPE:
-        case SensorType::GRAVITY:
-        case SensorType::LINEAR_ACCELERATION: {
-            dst->acceleration.x = src.payload.get<Event::EventPayload::vec3>().x;
-            dst->acceleration.y = src.payload.get<Event::EventPayload::vec3>().y;
-            dst->acceleration.z = src.payload.get<Event::EventPayload::vec3>().z;
-            dst->acceleration.status = (int32_t)src.payload.get<Event::EventPayload::vec3>().status;
-            break;
-        }
-
-        case SensorType::GAME_ROTATION_VECTOR: {
-            dst->data[0] = src.payload.get<Event::EventPayload::vec4>().x;
-            dst->data[1] = src.payload.get<Event::EventPayload::vec4>().y;
-            dst->data[2] = src.payload.get<Event::EventPayload::vec4>().z;
-            dst->data[3] = src.payload.get<Event::EventPayload::vec4>().w;
-            break;
-        }
-
-        case SensorType::ROTATION_VECTOR:
-        case SensorType::GEOMAGNETIC_ROTATION_VECTOR: {
-            dst->data[0] = src.payload.get<Event::EventPayload::data>().values[0];
-            dst->data[1] = src.payload.get<Event::EventPayload::data>().values[1];
-            dst->data[2] = src.payload.get<Event::EventPayload::data>().values[2];
-            dst->data[3] = src.payload.get<Event::EventPayload::data>().values[3];
-            dst->data[4] = src.payload.get<Event::EventPayload::data>().values[4];
-            break;
-        }
-
-        case SensorType::MAGNETIC_FIELD_UNCALIBRATED:
-        case SensorType::GYROSCOPE_UNCALIBRATED:
-        case SensorType::ACCELEROMETER_UNCALIBRATED: {
-            dst->uncalibrated_gyro.x_uncalib = src.payload.get<Event::EventPayload::uncal>().x;
-            dst->uncalibrated_gyro.y_uncalib = src.payload.get<Event::EventPayload::uncal>().y;
-            dst->uncalibrated_gyro.z_uncalib = src.payload.get<Event::EventPayload::uncal>().z;
-            dst->uncalibrated_gyro.x_bias = src.payload.get<Event::EventPayload::uncal>().xBias;
-            dst->uncalibrated_gyro.y_bias = src.payload.get<Event::EventPayload::uncal>().yBias;
-            dst->uncalibrated_gyro.z_bias = src.payload.get<Event::EventPayload::uncal>().zBias;
-            break;
-        }
-
-        case SensorType::HINGE_ANGLE:
-        case SensorType::DEVICE_ORIENTATION:
-        case SensorType::LIGHT:
-        case SensorType::PRESSURE:
-        case SensorType::PROXIMITY:
-        case SensorType::RELATIVE_HUMIDITY:
-        case SensorType::AMBIENT_TEMPERATURE:
-        case SensorType::SIGNIFICANT_MOTION:
-        case SensorType::STEP_DETECTOR:
-        case SensorType::TILT_DETECTOR:
-        case SensorType::WAKE_GESTURE:
-        case SensorType::GLANCE_GESTURE:
-        case SensorType::PICK_UP_GESTURE:
-        case SensorType::WRIST_TILT_GESTURE:
-        case SensorType::STATIONARY_DETECT:
-        case SensorType::MOTION_DETECT:
-        case SensorType::HEART_BEAT:
-        case SensorType::LOW_LATENCY_OFFBODY_DETECT: {
-            dst->data[0] = src.payload.get<Event::EventPayload::scalar>();
-            break;
-        }
-
-        case SensorType::STEP_COUNTER: {
-            dst->u64.step_counter = src.payload.get<Event::EventPayload::stepCount>();
-            break;
-        }
-
-        case SensorType::HEART_RATE: {
-            dst->heart_rate.bpm = src.payload.get<Event::EventPayload::heartRate>().bpm;
-            dst->heart_rate.status =
-                    (int8_t)src.payload.get<Event::EventPayload::heartRate>().status;
-            break;
-        }
-
-        case SensorType::POSE_6DOF: { // 15 floats
-            for (size_t i = 0; i < 15; ++i) {
-                dst->data[i] = src.payload.get<Event::EventPayload::pose6DOF>().values[i];
-            }
-            break;
-        }
-
-        case SensorType::DYNAMIC_SENSOR_META: {
-            dst->dynamic_sensor_meta.connected =
-                    src.payload.get<Event::EventPayload::dynamic>().connected;
-            dst->dynamic_sensor_meta.handle =
-                    src.payload.get<Event::EventPayload::dynamic>().sensorHandle;
-            dst->dynamic_sensor_meta.sensor = NULL; // to be filled in later
-
-            memcpy(dst->dynamic_sensor_meta.uuid,
-                   src.payload.get<Event::EventPayload::dynamic>().uuid.values.data(), 16);
-
-            break;
-        }
-
-        case SensorType::ADDITIONAL_INFO: {
-            const AdditionalInfo &srcInfo = src.payload.get<Event::EventPayload::additional>();
-
-            additional_info_event_t *dstInfo = &dst->additional_info;
-            dstInfo->type = (int32_t)srcInfo.type;
-            dstInfo->serial = srcInfo.serial;
-
-            switch (srcInfo.payload.getTag()) {
-                case AdditionalInfo::AdditionalInfoPayload::Tag::dataInt32: {
-                    const auto &values =
-                            srcInfo.payload.get<AdditionalInfo::AdditionalInfoPayload::dataInt32>()
-                                    .values;
-                    CHECK_EQ(values.size() * sizeof(int32_t), sizeof(dstInfo->data_int32));
-                    memcpy(dstInfo->data_int32, values.data(), sizeof(dstInfo->data_int32));
-                    break;
-                }
-                case AdditionalInfo::AdditionalInfoPayload::Tag::dataFloat: {
-                    const auto &values =
-                            srcInfo.payload.get<AdditionalInfo::AdditionalInfoPayload::dataFloat>()
-                                    .values;
-                    CHECK_EQ(values.size() * sizeof(float), sizeof(dstInfo->data_float));
-                    memcpy(dstInfo->data_float, values.data(), sizeof(dstInfo->data_float));
-                    break;
-                }
-                default: {
-                    ALOGE("Invalid sensor additional info tag: %d", (int)srcInfo.payload.getTag());
-                }
-            }
-            break;
-        }
-
-        case SensorType::HEAD_TRACKER: {
-            const auto &ht = src.payload.get<Event::EventPayload::headTracker>();
-            dst->head_tracker.rx = ht.rx;
-            dst->head_tracker.ry = ht.ry;
-            dst->head_tracker.rz = ht.rz;
-            dst->head_tracker.vx = ht.vx;
-            dst->head_tracker.vy = ht.vy;
-            dst->head_tracker.vz = ht.vz;
-            dst->head_tracker.discontinuity_count = ht.discontinuityCount;
-            break;
-        }
-
-        case SensorType::ACCELEROMETER_LIMITED_AXES:
-        case SensorType::GYROSCOPE_LIMITED_AXES:
-            dst->limited_axes_imu.x = src.payload.get<Event::EventPayload::limitedAxesImu>().x;
-            dst->limited_axes_imu.y = src.payload.get<Event::EventPayload::limitedAxesImu>().y;
-            dst->limited_axes_imu.z = src.payload.get<Event::EventPayload::limitedAxesImu>().z;
-            dst->limited_axes_imu.x_supported =
-                    src.payload.get<Event::EventPayload::limitedAxesImu>().xSupported;
-            dst->limited_axes_imu.y_supported =
-                    src.payload.get<Event::EventPayload::limitedAxesImu>().ySupported;
-            dst->limited_axes_imu.z_supported =
-                    src.payload.get<Event::EventPayload::limitedAxesImu>().zSupported;
-            break;
-
-        case SensorType::ACCELEROMETER_LIMITED_AXES_UNCALIBRATED:
-        case SensorType::GYROSCOPE_LIMITED_AXES_UNCALIBRATED:
-            dst->limited_axes_imu_uncalibrated.x_uncalib =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().x;
-            dst->limited_axes_imu_uncalibrated.y_uncalib =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().y;
-            dst->limited_axes_imu_uncalibrated.z_uncalib =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().z;
-            dst->limited_axes_imu_uncalibrated.x_bias =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().xBias;
-            dst->limited_axes_imu_uncalibrated.y_bias =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().yBias;
-            dst->limited_axes_imu_uncalibrated.z_bias =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().zBias;
-            dst->limited_axes_imu_uncalibrated.x_supported =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().xSupported;
-            dst->limited_axes_imu_uncalibrated.y_supported =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().ySupported;
-            dst->limited_axes_imu_uncalibrated.z_supported =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().zSupported;
-            break;
-
-        case SensorType::HEADING:
-            dst->heading.heading = src.payload.get<Event::EventPayload::heading>().heading;
-            dst->heading.accuracy = src.payload.get<Event::EventPayload::heading>().accuracy;
-            break;
-
-        default: {
-            CHECK_GE((int32_t)src.sensorType, (int32_t)SensorType::DEVICE_PRIVATE_BASE);
-
-            memcpy(dst->data, src.payload.get<Event::EventPayload::data>().values.data(),
-                   16 * sizeof(float));
-            break;
-        }
-    }
-}
-
-void convertFromSensorEvent(const sensors_event_t &src, Event *dst) {
-    *dst = {
-            .timestamp = src.timestamp,
-            .sensorHandle = src.sensor,
-            .sensorType = (SensorType)src.type,
-    };
-
-    switch (dst->sensorType) {
-        case SensorType::META_DATA: {
-            Event::EventPayload::MetaData meta;
-            meta.what = (Event::EventPayload::MetaData::MetaDataEventType)src.meta_data.what;
-            // Legacy HALs contain the handle reference in the meta data field.
-            // Copy that over to the handle of the event. In legacy HALs this
-            // field was expected to be 0.
-            dst->sensorHandle = src.meta_data.sensor;
-            dst->payload.set<Event::EventPayload::Tag::meta>(meta);
-            break;
-        }
-
-        case SensorType::ACCELEROMETER:
-        case SensorType::MAGNETIC_FIELD:
-        case SensorType::ORIENTATION:
-        case SensorType::GYROSCOPE:
-        case SensorType::GRAVITY:
-        case SensorType::LINEAR_ACCELERATION: {
-            Event::EventPayload::Vec3 vec3;
-            vec3.x = src.acceleration.x;
-            vec3.y = src.acceleration.y;
-            vec3.z = src.acceleration.z;
-            vec3.status = (SensorStatus)src.acceleration.status;
-            dst->payload.set<Event::EventPayload::Tag::vec3>(vec3);
-            break;
-        }
-
-        case SensorType::GAME_ROTATION_VECTOR: {
-            Event::EventPayload::Vec4 vec4;
-            vec4.x = src.data[0];
-            vec4.y = src.data[1];
-            vec4.z = src.data[2];
-            vec4.w = src.data[3];
-            dst->payload.set<Event::EventPayload::Tag::vec4>(vec4);
-            break;
-        }
-
-        case SensorType::ROTATION_VECTOR:
-        case SensorType::GEOMAGNETIC_ROTATION_VECTOR: {
-            Event::EventPayload::Data data;
-            memcpy(data.values.data(), src.data, 5 * sizeof(float));
-            dst->payload.set<Event::EventPayload::Tag::data>(data);
-            break;
-        }
-
-        case SensorType::MAGNETIC_FIELD_UNCALIBRATED:
-        case SensorType::GYROSCOPE_UNCALIBRATED:
-        case SensorType::ACCELEROMETER_UNCALIBRATED: {
-            Event::EventPayload::Uncal uncal;
-            uncal.x = src.uncalibrated_gyro.x_uncalib;
-            uncal.y = src.uncalibrated_gyro.y_uncalib;
-            uncal.z = src.uncalibrated_gyro.z_uncalib;
-            uncal.xBias = src.uncalibrated_gyro.x_bias;
-            uncal.yBias = src.uncalibrated_gyro.y_bias;
-            uncal.zBias = src.uncalibrated_gyro.z_bias;
-            dst->payload.set<Event::EventPayload::Tag::uncal>(uncal);
-            break;
-        }
-
-        case SensorType::DEVICE_ORIENTATION:
-        case SensorType::LIGHT:
-        case SensorType::PRESSURE:
-        case SensorType::PROXIMITY:
-        case SensorType::RELATIVE_HUMIDITY:
-        case SensorType::AMBIENT_TEMPERATURE:
-        case SensorType::SIGNIFICANT_MOTION:
-        case SensorType::STEP_DETECTOR:
-        case SensorType::TILT_DETECTOR:
-        case SensorType::WAKE_GESTURE:
-        case SensorType::GLANCE_GESTURE:
-        case SensorType::PICK_UP_GESTURE:
-        case SensorType::WRIST_TILT_GESTURE:
-        case SensorType::STATIONARY_DETECT:
-        case SensorType::MOTION_DETECT:
-        case SensorType::HEART_BEAT:
-        case SensorType::LOW_LATENCY_OFFBODY_DETECT:
-        case SensorType::HINGE_ANGLE: {
-            dst->payload.set<Event::EventPayload::Tag::scalar>((float)src.data[0]);
-            break;
-        }
-
-        case SensorType::STEP_COUNTER: {
-            dst->payload.set<Event::EventPayload::Tag::stepCount>(src.u64.step_counter);
-            break;
-        }
-
-        case SensorType::HEART_RATE: {
-            Event::EventPayload::HeartRate heartRate;
-            heartRate.bpm = src.heart_rate.bpm;
-            heartRate.status = (SensorStatus)src.heart_rate.status;
-            dst->payload.set<Event::EventPayload::Tag::heartRate>(heartRate);
-            break;
-        }
-
-        case SensorType::POSE_6DOF: { // 15 floats
-            Event::EventPayload::Pose6Dof pose6DOF;
-            for (size_t i = 0; i < 15; ++i) {
-                pose6DOF.values[i] = src.data[i];
-            }
-            dst->payload.set<Event::EventPayload::Tag::pose6DOF>(pose6DOF);
-            break;
-        }
-
-        case SensorType::DYNAMIC_SENSOR_META: {
-            DynamicSensorInfo dynamic;
-            dynamic.connected = src.dynamic_sensor_meta.connected;
-            dynamic.sensorHandle = src.dynamic_sensor_meta.handle;
-
-            memcpy(dynamic.uuid.values.data(), src.dynamic_sensor_meta.uuid, 16);
-            dst->payload.set<Event::EventPayload::Tag::dynamic>(dynamic);
-            break;
-        }
-
-        case SensorType::ADDITIONAL_INFO: {
-            AdditionalInfo info;
-            const additional_info_event_t &srcInfo = src.additional_info;
-            info.type = (AdditionalInfo::AdditionalInfoType)srcInfo.type;
-            info.serial = srcInfo.serial;
-
-            AdditionalInfo::AdditionalInfoPayload::Int32Values data;
-            CHECK_EQ(data.values.size() * sizeof(int32_t), sizeof(srcInfo.data_int32));
-            memcpy(data.values.data(), srcInfo.data_int32, sizeof(srcInfo.data_int32));
-            info.payload.set<AdditionalInfo::AdditionalInfoPayload::Tag::dataInt32>(data);
-
-            dst->payload.set<Event::EventPayload::Tag::additional>(info);
-            break;
-        }
-
-        case SensorType::HEAD_TRACKER: {
-            Event::EventPayload::HeadTracker headTracker;
-            headTracker.rx = src.head_tracker.rx;
-            headTracker.ry = src.head_tracker.ry;
-            headTracker.rz = src.head_tracker.rz;
-            headTracker.vx = src.head_tracker.vx;
-            headTracker.vy = src.head_tracker.vy;
-            headTracker.vz = src.head_tracker.vz;
-            headTracker.discontinuityCount = src.head_tracker.discontinuity_count;
-
-            dst->payload.set<Event::EventPayload::Tag::headTracker>(headTracker);
-            break;
-        }
-
-        case SensorType::ACCELEROMETER_LIMITED_AXES:
-        case SensorType::GYROSCOPE_LIMITED_AXES: {
-            Event::EventPayload::LimitedAxesImu limitedAxesImu;
-            limitedAxesImu.x = src.limited_axes_imu.x;
-            limitedAxesImu.y = src.limited_axes_imu.y;
-            limitedAxesImu.z = src.limited_axes_imu.z;
-            limitedAxesImu.xSupported = src.limited_axes_imu.x_supported;
-            limitedAxesImu.ySupported = src.limited_axes_imu.y_supported;
-            limitedAxesImu.zSupported = src.limited_axes_imu.z_supported;
-            dst->payload.set<Event::EventPayload::Tag::limitedAxesImu>(limitedAxesImu);
-            break;
-        }
-
-        case SensorType::ACCELEROMETER_LIMITED_AXES_UNCALIBRATED:
-        case SensorType::GYROSCOPE_LIMITED_AXES_UNCALIBRATED: {
-            Event::EventPayload::LimitedAxesImuUncal limitedAxesImuUncal;
-            limitedAxesImuUncal.x = src.limited_axes_imu_uncalibrated.x_uncalib;
-            limitedAxesImuUncal.y = src.limited_axes_imu_uncalibrated.y_uncalib;
-            limitedAxesImuUncal.z = src.limited_axes_imu_uncalibrated.z_uncalib;
-            limitedAxesImuUncal.xBias = src.limited_axes_imu_uncalibrated.x_bias;
-            limitedAxesImuUncal.yBias = src.limited_axes_imu_uncalibrated.y_bias;
-            limitedAxesImuUncal.zBias = src.limited_axes_imu_uncalibrated.z_bias;
-            limitedAxesImuUncal.xSupported = src.limited_axes_imu_uncalibrated.x_supported;
-            limitedAxesImuUncal.ySupported = src.limited_axes_imu_uncalibrated.y_supported;
-            limitedAxesImuUncal.zSupported = src.limited_axes_imu_uncalibrated.z_supported;
-            dst->payload.set<Event::EventPayload::Tag::limitedAxesImuUncal>(limitedAxesImuUncal);
-            break;
-        }
-
-        case SensorType::HEADING: {
-            Event::EventPayload::Heading heading;
-            heading.heading = src.heading.heading;
-            heading.accuracy = src.heading.accuracy;
-            dst->payload.set<Event::EventPayload::heading>(heading);
-            break;
-        }
-
-        default: {
-            CHECK_GE((int32_t)dst->sensorType, (int32_t)SensorType::DEVICE_PRIVATE_BASE);
-
-            Event::EventPayload::Data data;
-            memcpy(data.values.data(), src.data, 16 * sizeof(float));
-            dst->payload.set<Event::EventPayload::Tag::data>(data);
-            break;
-        }
-    }
-}
-
 void serviceDied(void *cookie) {
     ALOGW("Sensors HAL died, attempting to reconnect.");
     ((AidlSensorHalWrapper *)cookie)->prepareForReconnect();
diff --git a/services/sensorservice/Android.bp b/services/sensorservice/Android.bp
index 5ad4815..0eeb820 100644
--- a/services/sensorservice/Android.bp
+++ b/services/sensorservice/Android.bp
@@ -75,6 +75,7 @@
     static_libs: [
         "libaidlcommonsupport",
         "android.hardware.sensors@1.0-convert",
+        "android.hardware.sensors-V1-convert",
         "android.hardware.sensors-V1-ndk",
     ],
 
diff --git a/services/sensorservice/aidl/Android.bp b/services/sensorservice/aidl/Android.bp
new file mode 100644
index 0000000..34d1de7
--- /dev/null
+++ b/services/sensorservice/aidl/Android.bp
@@ -0,0 +1,44 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_library {
+    name: "libsensorserviceaidl",
+    srcs: [
+        "EventQueue.cpp",
+        "DirectReportChannel.cpp",
+        "SensorManager.cpp",
+        "utils.cpp",
+    ],
+    host_supported: true,
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    header_libs: ["jni_headers"],
+    shared_libs: [
+        "libbase",
+        "libutils",
+        "libcutils",
+        "libbinder_ndk",
+        "libsensor",
+        "android.frameworks.sensorservice-V1-ndk",
+        "android.hardware.sensors-V1-ndk",
+    ],
+    export_include_dirs: [
+        "include/",
+    ],
+    static_libs: [
+        "android.hardware.sensors-V1-convert",
+    ],
+
+    export_header_lib_headers: ["jni_headers"],
+    local_include_dirs: [
+        "include/sensorserviceaidl/",
+    ],
+}
diff --git a/services/sensorservice/aidl/DirectReportChannel.cpp b/services/sensorservice/aidl/DirectReportChannel.cpp
new file mode 100644
index 0000000..cab53c1
--- /dev/null
+++ b/services/sensorservice/aidl/DirectReportChannel.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "DirectReportChannel.h"
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+DirectReportChannel::DirectReportChannel(::android::SensorManager& manager, int channelId)
+      : mManager(manager), mId(channelId) {}
+
+DirectReportChannel::~DirectReportChannel() {
+    mManager.destroyDirectChannel(mId);
+}
+
+ndk::ScopedAStatus DirectReportChannel::configure(
+        int32_t sensorHandle, ::aidl::android::hardware::sensors::ISensors::RateLevel rate,
+        int32_t* _aidl_return) {
+    int token = mManager.configureDirectChannel(mId, sensorHandle, static_cast<int>(rate));
+    if (token <= 0) {
+        return ndk::ScopedAStatus::fromServiceSpecificError(token);
+    }
+    *_aidl_return = token;
+    return ndk::ScopedAStatus::ok();
+}
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/DirectReportChannel.h b/services/sensorservice/aidl/DirectReportChannel.h
new file mode 100644
index 0000000..d9ea73d
--- /dev/null
+++ b/services/sensorservice/aidl/DirectReportChannel.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <aidl/android/frameworks/sensorservice/BnDirectReportChannel.h>
+#include <aidl/android/hardware/sensors/ISensors.h>
+#include <sensor/SensorManager.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+class DirectReportChannel final
+      : public ::aidl::android::frameworks::sensorservice::BnDirectReportChannel {
+public:
+    DirectReportChannel(::android::SensorManager& manager, int channelId);
+    ~DirectReportChannel();
+
+    ndk::ScopedAStatus configure(int32_t sensorHandle,
+                                 ::aidl::android::hardware::sensors::ISensors::RateLevel rate,
+                                 int32_t* _aidl_return) override;
+
+private:
+    ::android::SensorManager& mManager;
+    const int mId;
+};
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/EventQueue.cpp b/services/sensorservice/aidl/EventQueue.cpp
new file mode 100644
index 0000000..88ab7a7
--- /dev/null
+++ b/services/sensorservice/aidl/EventQueue.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "EventQueue.h"
+#include "utils.h"
+
+#include <android-base/logging.h>
+#include <utils/Looper.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+using ::aidl::android::frameworks::sensorservice::IEventQueueCallback;
+using ::aidl::android::hardware::sensors::Event;
+
+class EventQueueLooperCallback : public ::android::LooperCallback {
+public:
+    EventQueueLooperCallback(sp<::android::SensorEventQueue> queue,
+                             std::shared_ptr<IEventQueueCallback> callback)
+          : mQueue(queue), mCallback(callback) {}
+
+    int handleEvent(int /* fd */, int /* events */, void* /* data */) {
+        ASensorEvent event;
+        ssize_t actual;
+
+        auto internalQueue = mQueue.promote();
+        if (internalQueue == nullptr) {
+            return 1;
+        }
+
+        while ((actual = internalQueue->read(&event, 1)) > 0) {
+            internalQueue->sendAck(&event, actual);
+            ndk::ScopedAStatus ret = mCallback->onEvent(convertEvent(event));
+            if (!ret.isOk()) {
+                LOG(ERROR) << "Failed to envoke EventQueueCallback: " << ret;
+            }
+        }
+
+        return 1; // continue to receive callbacks
+    }
+
+private:
+    wp<::android::SensorEventQueue> mQueue;
+    std::shared_ptr<IEventQueueCallback> mCallback;
+};
+
+EventQueue::EventQueue(std::shared_ptr<IEventQueueCallback> callback, sp<::android::Looper> looper,
+                       sp<::android::SensorEventQueue> internalQueue)
+      : mLooper(looper), mInternalQueue(internalQueue) {
+    mLooper->addFd(internalQueue->getFd(), ALOOPER_POLL_CALLBACK, ALOOPER_EVENT_INPUT,
+                   new EventQueueLooperCallback(internalQueue, callback), nullptr);
+}
+
+// FIXME why was this on onLastStrongRef instead of dtor?
+EventQueue::~EventQueue() {
+    mLooper->removeFd(mInternalQueue->getFd());
+}
+
+ndk::ScopedAStatus EventQueue::enableSensor(int32_t in_sensorHandle, int32_t in_samplingPeriodUs,
+                                            int64_t in_maxBatchReportLatencyUs) {
+    return convertResult(mInternalQueue->enableSensor(in_sensorHandle, in_samplingPeriodUs,
+                                                      in_maxBatchReportLatencyUs, 0));
+}
+
+ndk::ScopedAStatus EventQueue::disableSensor(int32_t in_sensorHandle) {
+    return convertResult(mInternalQueue->disableSensor(in_sensorHandle));
+}
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/EventQueue.h b/services/sensorservice/aidl/EventQueue.h
new file mode 100644
index 0000000..0ae1eba
--- /dev/null
+++ b/services/sensorservice/aidl/EventQueue.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "SensorManagerAidl.h"
+
+#include <aidl/android/frameworks/sensorservice/BnEventQueue.h>
+#include <sensor/SensorManager.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+struct EventQueue final : public aidl::android::frameworks::sensorservice::BnEventQueue {
+    EventQueue(
+            std::shared_ptr<aidl::android::frameworks::sensorservice::IEventQueueCallback> callback,
+            sp<::android::Looper> looper, sp<::android::SensorEventQueue> internalQueue);
+    ~EventQueue();
+
+    ndk::ScopedAStatus enableSensor(int32_t in_sensorHandle, int32_t in_samplingPeriodUs,
+                                    int64_t in_maxBatchReportLatencyUs) override;
+    ndk::ScopedAStatus disableSensor(int32_t sensorHandle) override;
+
+private:
+    friend class EventQueueLooperCallback;
+    sp<::android::Looper> mLooper;
+    sp<::android::SensorEventQueue> mInternalQueue;
+};
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/SensorManager.cpp b/services/sensorservice/aidl/SensorManager.cpp
new file mode 100644
index 0000000..6d8d574
--- /dev/null
+++ b/services/sensorservice/aidl/SensorManager.cpp
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// LOG_TAG defined via build flag.
+#ifndef LOG_TAG
+#define LOG_TAG "AidlSensorManager"
+#endif
+
+#include "DirectReportChannel.h"
+#include "EventQueue.h"
+#include "SensorManagerAidl.h"
+#include "utils.h"
+
+#include <aidl/android/hardware/sensors/ISensors.h>
+#include <android-base/logging.h>
+#include <android/binder_ibinder.h>
+#include <sched.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+using ::aidl::android::frameworks::sensorservice::IDirectReportChannel;
+using ::aidl::android::frameworks::sensorservice::IEventQueue;
+using ::aidl::android::frameworks::sensorservice::IEventQueueCallback;
+using ::aidl::android::frameworks::sensorservice::ISensorManager;
+using ::aidl::android::hardware::common::Ashmem;
+using ::aidl::android::hardware::sensors::ISensors;
+using ::aidl::android::hardware::sensors::SensorInfo;
+using ::aidl::android::hardware::sensors::SensorType;
+using ::android::frameworks::sensorservice::implementation::SensorManagerAidl;
+
+static const char* POLL_THREAD_NAME = "aidl_ssvc_poll";
+
+SensorManagerAidl::SensorManagerAidl(JavaVM* vm)
+      : mLooper(new Looper(false)), mStopThread(true), mJavaVm(vm) {}
+SensorManagerAidl::~SensorManagerAidl() {
+    // Stops pollAll inside the thread.
+    std::lock_guard<std::mutex> lock(mThreadMutex);
+
+    mStopThread = true;
+    if (mLooper != nullptr) {
+        mLooper->wake();
+    }
+    if (mPollThread.joinable()) {
+        mPollThread.join();
+    }
+}
+
+ndk::ScopedAStatus createDirectChannel(::android::SensorManager& manager, size_t size, int type,
+                                       const native_handle_t* handle,
+                                       std::shared_ptr<IDirectReportChannel>* chan) {
+    int channelId = manager.createDirectChannel(size, type, handle);
+    if (channelId < 0) {
+        return convertResult(channelId);
+    }
+    if (channelId == 0) {
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_UNKNOWN_ERROR);
+    }
+    *chan = ndk::SharedRefBase::make<DirectReportChannel>(manager, channelId);
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus SensorManagerAidl::createAshmemDirectChannel(
+        const Ashmem& in_mem, int64_t in_size,
+        std::shared_ptr<IDirectReportChannel>* _aidl_return) {
+    if (in_size > in_mem.size || in_size < ISensors::DIRECT_REPORT_SENSOR_EVENT_TOTAL_LENGTH) {
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_BAD_VALUE);
+    }
+    native_handle_t* handle = native_handle_create(1, 0);
+    handle->data[0] = dup(in_mem.fd.get());
+
+    auto status = createDirectChannel(getInternalManager(), in_size, SENSOR_DIRECT_MEM_TYPE_ASHMEM,
+                                      handle, _aidl_return);
+    int result = native_handle_close(handle);
+    CHECK(result == 0) << "Failed to close the native_handle_t: " << result;
+    result = native_handle_delete(handle);
+    CHECK(result == 0) << "Failed to delete the native_handle_t: " << result;
+
+    return status;
+}
+
+ndk::ScopedAStatus SensorManagerAidl::createGrallocDirectChannel(
+        const ndk::ScopedFileDescriptor& in_mem, int64_t in_size,
+        std::shared_ptr<IDirectReportChannel>* _aidl_return) {
+    native_handle_t* handle = native_handle_create(1, 0);
+    handle->data[0] = dup(in_mem.get());
+
+    auto status = createDirectChannel(getInternalManager(), in_size, SENSOR_DIRECT_MEM_TYPE_GRALLOC,
+                                      handle, _aidl_return);
+    int result = native_handle_close(handle);
+    CHECK(result == 0) << "Failed to close the native_handle_t: " << result;
+    result = native_handle_delete(handle);
+    CHECK(result == 0) << "Failed to delete the native_handle_t: " << result;
+
+    return status;
+}
+
+ndk::ScopedAStatus SensorManagerAidl::createEventQueue(
+        const std::shared_ptr<IEventQueueCallback>& in_callback,
+        std::shared_ptr<IEventQueue>* _aidl_return) {
+    if (in_callback == nullptr) {
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_BAD_VALUE);
+    }
+
+    sp<::android::Looper> looper = getLooper();
+    if (looper == nullptr) {
+        LOG(ERROR) << "::android::SensorManagerAidl::createEventQueue cannot initialize looper";
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_UNKNOWN_ERROR);
+    }
+
+    String8 package(String8::format("aidl_client_pid_%d", AIBinder_getCallingPid()));
+    sp<::android::SensorEventQueue> internalQueue = getInternalManager().createEventQueue(package);
+    if (internalQueue == nullptr) {
+        LOG(ERROR) << "::android::SensorManagerAidl::createEventQueue returns nullptr.";
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_UNKNOWN_ERROR);
+    }
+
+    *_aidl_return = ndk::SharedRefBase::make<EventQueue>(in_callback, looper, internalQueue);
+
+    return ndk::ScopedAStatus::ok();
+}
+
+SensorInfo convertSensor(Sensor src) {
+    SensorInfo dst;
+    dst.sensorHandle = src.getHandle();
+    dst.name = src.getName();
+    dst.vendor = src.getVendor();
+    dst.version = src.getVersion();
+    dst.type = static_cast<SensorType>(src.getType());
+    dst.typeAsString = src.getStringType();
+    // maxRange uses maxValue because ::android::Sensor wraps the
+    // internal sensor_t in this way.
+    dst.maxRange = src.getMaxValue();
+    dst.resolution = src.getResolution();
+    dst.power = src.getPowerUsage();
+    dst.minDelayUs = src.getMinDelay();
+    dst.fifoReservedEventCount = src.getFifoReservedEventCount();
+    dst.fifoMaxEventCount = src.getFifoMaxEventCount();
+    dst.requiredPermission = src.getRequiredPermission();
+    dst.maxDelayUs = src.getMaxDelay();
+    dst.flags = src.getFlags();
+    return dst;
+}
+
+ndk::ScopedAStatus SensorManagerAidl::getDefaultSensor(SensorType in_type,
+                                                       SensorInfo* _aidl_return) {
+    ::android::Sensor const* sensor =
+            getInternalManager().getDefaultSensor(static_cast<int>(in_type));
+    if (!sensor) {
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_NOT_EXIST);
+    }
+    *_aidl_return = convertSensor(*sensor);
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus SensorManagerAidl::getSensorList(std::vector<SensorInfo>* _aidl_return) {
+    Sensor const* const* list;
+    _aidl_return->clear();
+    ssize_t count = getInternalManager().getSensorList(&list);
+    if (count < 0 || list == nullptr) {
+        LOG(ERROR) << "SensorMAanger::getSensorList failed with count: " << count;
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_UNKNOWN_ERROR);
+    }
+    _aidl_return->reserve(static_cast<size_t>(count));
+    for (ssize_t i = 0; i < count; ++i) {
+        _aidl_return->push_back(convertSensor(*list[i]));
+    }
+
+    return ndk::ScopedAStatus::ok();
+}
+
+::android::SensorManager& SensorManagerAidl::getInternalManager() {
+    std::lock_guard<std::mutex> lock(mInternalManagerMutex);
+    if (mInternalManager == nullptr) {
+        mInternalManager = &::android::SensorManager::getInstanceForPackage(
+                String16(ISensorManager::descriptor));
+    }
+    return *mInternalManager;
+}
+
+/* One global looper for all event queues created from this SensorManager. */
+sp<Looper> SensorManagerAidl::getLooper() {
+    std::lock_guard<std::mutex> lock(mThreadMutex);
+
+    if (!mPollThread.joinable()) {
+        // if thread not initialized, start thread
+        mStopThread = false;
+        std::thread pollThread{[&stopThread = mStopThread, looper = mLooper, javaVm = mJavaVm] {
+            struct sched_param p = {0};
+            p.sched_priority = 10;
+            if (sched_setscheduler(0 /* current thread*/, SCHED_FIFO, &p) != 0) {
+                LOG(ERROR) << "Could not use SCHED_FIFO for looper thread: " << strerror(errno);
+            }
+
+            // set looper
+            Looper::setForThread(looper);
+
+            // Attach the thread to JavaVM so that pollAll do not crash if the thread
+            // eventually calls into Java.
+            JavaVMAttachArgs args{.version = JNI_VERSION_1_2,
+                                  .name = POLL_THREAD_NAME,
+                                  .group = nullptr};
+            JNIEnv* env;
+            if (javaVm->AttachCurrentThread(&env, &args) != JNI_OK) {
+                LOG(FATAL) << "Cannot attach SensorManager looper thread to Java VM.";
+            }
+
+            LOG(INFO) << POLL_THREAD_NAME << " started.";
+            for (;;) {
+                int pollResult = looper->pollAll(-1 /* timeout */);
+                if (pollResult == Looper::POLL_WAKE) {
+                    if (stopThread == true) {
+                        LOG(INFO) << POLL_THREAD_NAME << ": requested to stop";
+                        break;
+                    } else {
+                        LOG(INFO) << POLL_THREAD_NAME << ": spurious wake up, back to work";
+                    }
+                } else {
+                    LOG(ERROR) << POLL_THREAD_NAME << ": Looper::pollAll returns unexpected "
+                               << pollResult;
+                    break;
+                }
+            }
+
+            if (javaVm->DetachCurrentThread() != JNI_OK) {
+                LOG(ERROR) << "Cannot detach SensorManager looper thread from Java VM.";
+            }
+
+            LOG(INFO) << POLL_THREAD_NAME << " is terminated.";
+        }};
+        mPollThread = std::move(pollThread);
+    }
+    return mLooper;
+}
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/fuzzer/Android.bp b/services/sensorservice/aidl/fuzzer/Android.bp
new file mode 100644
index 0000000..0d6e476
--- /dev/null
+++ b/services/sensorservice/aidl/fuzzer/Android.bp
@@ -0,0 +1,52 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_fuzz {
+    name: "libsensorserviceaidl_fuzzer",
+    defaults: [
+        "service_fuzzer_defaults",
+    ],
+    host_supported: true,
+    static_libs: [
+        "libsensorserviceaidl",
+        "libpermission",
+        "android.frameworks.sensorservice-V1-ndk",
+        "android.hardware.sensors-V1-convert",
+        "android.hardware.sensors-V1-ndk",
+        "android.hardware.common-V2-ndk",
+        "libsensor",
+        "libfakeservicemanager",
+        "libcutils",
+        "liblog",
+    ],
+    srcs: [
+        "fuzzer.cpp",
+    ],
+    fuzz_config: {
+        cc: [
+            "android-sensors@google.com",
+            "devinmoore@google.com",
+        ],
+    },
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+            "unsigned-integer-overflow",
+        ],
+        diag: {
+            misc_undefined: [
+                "signed-integer-overflow",
+                "unsigned-integer-overflow",
+            ],
+        },
+        address: true,
+        integer_overflow: true,
+    },
+
+}
diff --git a/services/sensorservice/aidl/fuzzer/fuzzer.cpp b/services/sensorservice/aidl/fuzzer/fuzzer.cpp
new file mode 100644
index 0000000..1b63d76
--- /dev/null
+++ b/services/sensorservice/aidl/fuzzer/fuzzer.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <fuzzbinder/libbinder_ndk_driver.h>
+#include <fuzzer/FuzzedDataProvider.h>
+
+#include <ServiceManager.h>
+#include <android-base/logging.h>
+#include <android/binder_interface_utils.h>
+#include <fuzzbinder/random_binder.h>
+#include <sensorserviceaidl/SensorManagerAidl.h>
+
+using android::fuzzService;
+using android::frameworks::sensorservice::implementation::SensorManagerAidl;
+using ndk::SharedRefBase;
+
+[[clang::no_destroy]] static std::once_flag gSmOnce;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    static android::sp<android::ServiceManager> fakeServiceManager = new android::ServiceManager();
+    std::call_once(gSmOnce, [&] { setDefaultServiceManager(fakeServiceManager); });
+    fakeServiceManager->clear();
+
+    FuzzedDataProvider fdp(data, size);
+    android::sp<android::IBinder> binder = android::getRandomBinder(&fdp);
+    if (binder == nullptr) {
+        // Nothing to do if we get a null binder. It will cause SensorManager to
+        // hang while trying to get sensorservice.
+        return 0;
+    }
+
+    CHECK(android::NO_ERROR == fakeServiceManager->addService(android::String16("sensorservice"),
+                                   binder));
+
+    std::shared_ptr<SensorManagerAidl> sensorService =
+            ndk::SharedRefBase::make<SensorManagerAidl>(nullptr);
+
+    fuzzService(sensorService->asBinder().get(), std::move(fdp));
+
+    return 0;
+}
diff --git a/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h b/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h
new file mode 100644
index 0000000..c77ee88
--- /dev/null
+++ b/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/frameworks/sensorservice/BnSensorManager.h>
+#include <jni.h>
+#include <sensor/SensorManager.h>
+#include <utils/Looper.h>
+#include <mutex>
+#include <thread>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+class SensorManagerAidl : public ::aidl::android::frameworks::sensorservice::BnSensorManager {
+public:
+    explicit SensorManagerAidl(JavaVM* vm);
+    ~SensorManagerAidl();
+
+    ::ndk::ScopedAStatus createAshmemDirectChannel(
+            const ::aidl::android::hardware::common::Ashmem& in_mem, int64_t in_size,
+            std::shared_ptr<::aidl::android::frameworks::sensorservice::IDirectReportChannel>*
+                    _aidl_return) override;
+    ::ndk::ScopedAStatus createEventQueue(
+            const std::shared_ptr<::aidl::android::frameworks::sensorservice::IEventQueueCallback>&
+                    in_callback,
+            std::shared_ptr<::aidl::android::frameworks::sensorservice::IEventQueue>* _aidl_return)
+            override;
+    ::ndk::ScopedAStatus createGrallocDirectChannel(
+            const ::ndk::ScopedFileDescriptor& in_buffer, int64_t in_size,
+            std::shared_ptr<::aidl::android::frameworks::sensorservice::IDirectReportChannel>*
+                    _aidl_return) override;
+    ::ndk::ScopedAStatus getDefaultSensor(
+            ::aidl::android::hardware::sensors::SensorType in_type,
+            ::aidl::android::hardware::sensors::SensorInfo* _aidl_return) override;
+    ::ndk::ScopedAStatus getSensorList(
+            std::vector<::aidl::android::hardware::sensors::SensorInfo>* _aidl_return) override;
+
+private:
+    // Block until ::android::SensorManager is initialized.
+    ::android::SensorManager& getInternalManager();
+    sp<Looper> getLooper();
+
+    std::mutex mInternalManagerMutex;
+    ::android::SensorManager* mInternalManager = nullptr; // does not own
+    sp<Looper> mLooper;
+
+    volatile bool mStopThread;
+    std::mutex mThreadMutex; // protects mPollThread
+    std::thread mPollThread;
+
+    JavaVM* mJavaVm;
+};
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/utils.cpp b/services/sensorservice/aidl/utils.cpp
new file mode 100644
index 0000000..26bcdc5
--- /dev/null
+++ b/services/sensorservice/aidl/utils.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils.h"
+
+#include <aidl/android/frameworks/sensorservice/ISensorManager.h>
+#include <aidl/sensors/convert.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+ndk::ScopedAStatus convertResult(status_t src) {
+    using ::aidl::android::frameworks::sensorservice::ISensorManager;
+
+    int err = 0;
+    switch (src) {
+        case OK:
+            return ndk::ScopedAStatus::ok();
+        case NAME_NOT_FOUND:
+            err = ISensorManager::RESULT_NOT_EXIST;
+            break;
+        case NO_MEMORY:
+            err = ISensorManager::RESULT_NO_MEMORY;
+            break;
+        case NO_INIT:
+            err = ISensorManager::RESULT_NO_INIT;
+            break;
+        case PERMISSION_DENIED:
+            err = ISensorManager::RESULT_PERMISSION_DENIED;
+            break;
+        case BAD_VALUE:
+            err = ISensorManager::RESULT_BAD_VALUE;
+            break;
+        case INVALID_OPERATION:
+            err = ISensorManager::RESULT_INVALID_OPERATION;
+            break;
+        default:
+            err = ISensorManager::RESULT_UNKNOWN_ERROR;
+    }
+    return ndk::ScopedAStatus::fromServiceSpecificError(err);
+}
+
+::aidl::android::hardware::sensors::Event convertEvent(const ::ASensorEvent& src) {
+    ::aidl::android::hardware::sensors::Event dst;
+    ::android::hardware::sensors::implementation::
+            convertFromSensorEvent(reinterpret_cast<const sensors_event_t&>(src), &dst);
+    return dst;
+}
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/utils.h b/services/sensorservice/aidl/utils.h
new file mode 100644
index 0000000..06ba59e
--- /dev/null
+++ b/services/sensorservice/aidl/utils.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <aidl/android/hardware/sensors/Event.h>
+#include <sensor/Sensor.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+::ndk::ScopedAStatus convertResult(status_t status);
+::aidl::android::hardware::sensors::Event convertEvent(const ::ASensorEvent& event);
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 14fdd12..b1bd705 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -157,6 +157,8 @@
         "EventLog/EventLog.cpp",
         "FrontEnd/LayerCreationArgs.cpp",
         "FrontEnd/LayerHandle.cpp",
+        "FrontEnd/LayerLifecycleManager.cpp",
+        "FrontEnd/RequestedLayerState.cpp",
         "FrontEnd/TransactionHandler.cpp",
         "FlagManager.cpp",
         "FpsReporter.cpp",
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 758b346..33caa7a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -139,8 +139,8 @@
     MOCK_METHOD(Hwc2::AidlTransform, getPhysicalDisplayOrientation, (PhysicalDisplayId),
                 (const, override));
     MOCK_METHOD(bool, getValidateSkipped, (HalDisplayId), (const, override));
-    MOCK_METHOD(status_t, getOverlaySupport,
-                (aidl::android::hardware::graphics::composer3::OverlayProperties*));
+    MOCK_METHOD(const aidl::android::hardware::graphics::composer3::OverlayProperties&,
+                getOverlaySupport, (), (const, override));
 };
 
 } // namespace mock
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 6e522d3..b40c6d1 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -41,7 +41,7 @@
 
 #include "Display/DisplaySnapshot.h"
 #include "DisplayDevice.h"
-#include "FrontEnd/FrontEndDisplayInfo.h"
+#include "FrontEnd/DisplayInfo.h"
 #include "Layer.h"
 #include "RefreshRateOverlay.h"
 #include "SurfaceFlinger.h"
@@ -134,7 +134,7 @@
     }
 }
 
-auto DisplayDevice::getFrontEndInfo() const -> FrontEndDisplayInfo {
+auto DisplayDevice::getFrontEndInfo() const -> frontend::DisplayInfo {
     gui::DisplayInfo info;
     info.displayId = getLayerStack().id;
 
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index afa13e5..4b64aab 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -45,7 +45,7 @@
 #include "DisplayHardware/DisplayMode.h"
 #include "DisplayHardware/Hal.h"
 #include "DisplayHardware/PowerAdvisor.h"
-#include "FrontEnd/FrontEndDisplayInfo.h"
+#include "FrontEnd/DisplayInfo.h"
 #include "Scheduler/RefreshRateSelector.h"
 #include "ThreadContext.h"
 #include "TracedOrdinal.h"
@@ -167,7 +167,7 @@
     void setDisplayName(const std::string& displayName);
     const std::string& getDisplayName() const { return mDisplayName; }
 
-    surfaceflinger::FrontEndDisplayInfo getFrontEndInfo() const;
+    surfaceflinger::frontend::DisplayInfo getFrontEndInfo() const;
 
     /* ------------------------------------------------------------------------
      * Display power mode management.
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index eff5130..3782c6c 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -542,8 +542,12 @@
     return Error::NONE;
 }
 
-Error AidlComposer::getOverlaySupport(AidlOverlayProperties* /*outProperties*/) {
-    // TODO(b/242588489): implement details
+Error AidlComposer::getOverlaySupport(AidlOverlayProperties* outProperties) {
+    const auto status = mAidlComposerClient->getOverlaySupport(outProperties);
+    if (!status.isOk()) {
+        ALOGE("getOverlaySupport failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
     return Error::NONE;
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index a9337d8..e264570 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -334,9 +334,9 @@
     return Error::NONE;
 }
 
-Error Display::getOverlaySupport(OverlayProperties* /*outProperties*/) const {
-    // TODO(b/242588489): implement details
-    return Error::NONE;
+Error Display::getOverlaySupport(OverlayProperties* outProperties) const {
+    auto intError = mComposer.getOverlaySupport(outProperties);
+    return static_cast<Error>(intError);
 }
 
 Error Display::getDisplayedContentSamplingAttributes(hal::PixelFormat* outFormat,
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 5f11cb8..10fde2a 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -96,6 +96,7 @@
 void HWComposer::setCallback(HWC2::ComposerCallback& callback) {
     loadCapabilities();
     loadLayerMetadataSupport();
+    loadOverlayProperties();
 
     if (mRegisteredCallback) {
         ALOGW("Callback already registered. Ignored extra registration attempt.");
@@ -652,9 +653,9 @@
     return NO_ERROR;
 }
 
-status_t HWComposer::getOverlaySupport(
-        aidl::android::hardware::graphics::composer3::OverlayProperties* /*outProperties*/) {
-    return NO_ERROR;
+const aidl::android::hardware::graphics::composer3::OverlayProperties&
+HWComposer::getOverlaySupport() const {
+    return mOverlayProperties;
 }
 
 int32_t HWComposer::getSupportedPerFrameMetadata(HalDisplayId displayId) const {
@@ -974,6 +975,10 @@
     }
 }
 
+void HWComposer::loadOverlayProperties() {
+    mComposer->getOverlaySupport(&mOverlayProperties);
+}
+
 status_t HWComposer::setIdleTimerEnabled(PhysicalDisplayId displayId,
                                          std::chrono::milliseconds timeout) {
     ATRACE_CALL();
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 8235b88..78d4a68 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -179,8 +179,8 @@
     // Fetches the HDR capabilities of the given display
     virtual status_t getHdrCapabilities(HalDisplayId, HdrCapabilities* outCapabilities) = 0;
 
-    virtual status_t getOverlaySupport(
-            aidl::android::hardware::graphics::composer3::OverlayProperties* outProperties) = 0;
+    virtual const aidl::android::hardware::graphics::composer3::OverlayProperties&
+    getOverlaySupport() const = 0;
 
     virtual int32_t getSupportedPerFrameMetadata(HalDisplayId) const = 0;
 
@@ -366,8 +366,8 @@
     // Fetches the HDR capabilities of the given display
     status_t getHdrCapabilities(HalDisplayId, HdrCapabilities* outCapabilities) override;
 
-    status_t getOverlaySupport(aidl::android::hardware::graphics::composer3::OverlayProperties*
-                                       outProperties) override;
+    const aidl::android::hardware::graphics::composer3::OverlayProperties& getOverlaySupport()
+            const override;
 
     int32_t getSupportedPerFrameMetadata(HalDisplayId) const override;
 
@@ -489,11 +489,13 @@
 
     void loadCapabilities();
     void loadLayerMetadataSupport();
+    void loadOverlayProperties();
 
     std::unordered_map<HalDisplayId, DisplayData> mDisplayData;
 
     std::unique_ptr<android::Hwc2::Composer> mComposer;
     std::unordered_set<aidl::android::hardware::graphics::composer3::Capability> mCapabilities;
+    aidl::android::hardware::graphics::composer3::OverlayProperties mOverlayProperties;
     std::unordered_map<std::string, bool> mSupportedLayerGenericMetadata;
     bool mRegisteredCallback = false;
 
diff --git a/services/surfaceflinger/FrontEnd/FrontEndDisplayInfo.h b/services/surfaceflinger/FrontEnd/DisplayInfo.h
similarity index 84%
rename from services/surfaceflinger/FrontEnd/FrontEndDisplayInfo.h
rename to services/surfaceflinger/FrontEnd/DisplayInfo.h
index 95e69b3..0c7b24a 100644
--- a/services/surfaceflinger/FrontEnd/FrontEndDisplayInfo.h
+++ b/services/surfaceflinger/FrontEnd/DisplayInfo.h
@@ -18,11 +18,10 @@
 
 #include <gui/DisplayInfo.h>
 
-// TODO (b/259553365) fix namespace to be consistent with other components
-namespace android::surfaceflinger {
+namespace android::surfaceflinger::frontend {
 
 // Display information needed to populate input and calculate layer geometry.
-struct FrontEndDisplayInfo {
+struct DisplayInfo {
     gui::DisplayInfo info;
     ui::Transform transform;
     bool receivesInput;
@@ -32,4 +31,4 @@
     ui::Transform::RotationFlags rotationFlags;
 };
 
-} // namespace android::surfaceflinger
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
new file mode 100644
index 0000000..1108246
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -0,0 +1,317 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#undef LOG_TAG
+#define LOG_TAG "LayerLifecycleManager"
+
+#include "LayerLifecycleManager.h"
+#include "Layer.h" // temporarily needed for LayerHandle
+#include "LayerHandle.h"
+#include "SwapErase.h"
+
+namespace android::surfaceflinger::frontend {
+
+using namespace ftl::flag_operators;
+
+void LayerLifecycleManager::addLayers(std::vector<std::unique_ptr<RequestedLayerState>> newLayers) {
+    if (newLayers.empty()) {
+        return;
+    }
+
+    mGlobalChanges |= RequestedLayerState::Changes::Hierarchy;
+    for (auto& newLayer : newLayers) {
+        RequestedLayerState& layer = *newLayer.get();
+        auto [it, inserted] = mIdToLayer.try_emplace(layer.id, References{.owner = layer});
+        if (!inserted) {
+            LOG_ALWAYS_FATAL("Duplicate layer id %d found. Existing layer: %s", layer.id,
+                             it->second.owner.getDebugString().c_str());
+        }
+
+        linkLayer(layer.parentId, layer.id);
+        linkLayer(layer.relativeParentId, layer.id);
+        linkLayer(layer.mirrorId, layer.id);
+        linkLayer(layer.touchCropId, layer.id);
+
+        mLayers.emplace_back(std::move(newLayer));
+    }
+}
+
+void LayerLifecycleManager::onHandlesDestroyed(const std::vector<uint32_t>& destroyedHandles) {
+    std::vector<uint32_t> layersToBeDestroyed;
+    for (const auto& layerId : destroyedHandles) {
+        auto it = mIdToLayer.find(layerId);
+        if (it == mIdToLayer.end()) {
+            LOG_ALWAYS_FATAL("%s Layerid not found %d", __func__, layerId);
+            continue;
+        }
+        RequestedLayerState& layer = it->second.owner;
+        layer.handleAlive = false;
+        if (!layer.canBeDestroyed()) {
+            continue;
+        }
+        layer.changes |= RequestedLayerState::Changes::Destroyed;
+        layersToBeDestroyed.emplace_back(layerId);
+    }
+
+    if (layersToBeDestroyed.empty()) {
+        return;
+    }
+
+    mGlobalChanges |= RequestedLayerState::Changes::Hierarchy;
+    for (size_t i = 0; i < layersToBeDestroyed.size(); i++) {
+        uint32_t layerId = layersToBeDestroyed[i];
+        auto it = mIdToLayer.find(layerId);
+        if (it == mIdToLayer.end()) {
+            LOG_ALWAYS_FATAL("%s Layer with id %d not found", __func__, layerId);
+            continue;
+        }
+
+        RequestedLayerState& layer = it->second.owner;
+
+        unlinkLayer(layer.parentId, layer.id);
+        unlinkLayer(layer.relativeParentId, layer.id);
+        unlinkLayer(layer.mirrorId, layer.id);
+        unlinkLayer(layer.touchCropId, layer.id);
+
+        auto& references = it->second.references;
+        for (uint32_t linkedLayerId : references) {
+            RequestedLayerState* linkedLayer = getLayerFromId(linkedLayerId);
+            if (!linkedLayer) {
+                LOG_ALWAYS_FATAL("%s Layerid reference %d not found for %d", __func__,
+                                 linkedLayerId, layer.id);
+                continue;
+            };
+            if (linkedLayer->parentId == layer.id) {
+                linkedLayer->parentId = UNASSIGNED_LAYER_ID;
+                if (linkedLayer->canBeDestroyed()) {
+                    linkedLayer->changes |= RequestedLayerState::Changes::Destroyed;
+                    layersToBeDestroyed.emplace_back(linkedLayer->id);
+                }
+            }
+            if (linkedLayer->relativeParentId == layer.id) {
+                linkedLayer->relativeParentId = UNASSIGNED_LAYER_ID;
+            }
+            if (linkedLayer->mirrorId == layer.id) {
+                linkedLayer->mirrorId = UNASSIGNED_LAYER_ID;
+            }
+            if (linkedLayer->touchCropId == layer.id) {
+                linkedLayer->touchCropId = UNASSIGNED_LAYER_ID;
+            }
+        }
+        mIdToLayer.erase(it);
+    }
+
+    auto it = mLayers.begin();
+    while (it != mLayers.end()) {
+        RequestedLayerState* layer = it->get();
+        if (layer->changes.test(RequestedLayerState::Changes::Destroyed)) {
+            ALOGV("%s destroyed layer %s", __func__, layer->getDebugStringShort().c_str());
+            std::iter_swap(it, mLayers.end() - 1);
+            mDestroyedLayers.emplace_back(std::move(mLayers.back()));
+            mLayers.erase(mLayers.end() - 1);
+        } else {
+            it++;
+        }
+    }
+}
+
+void LayerLifecycleManager::applyTransactions(const std::vector<TransactionState>& transactions) {
+    for (const auto& transaction : transactions) {
+        for (const auto& resolvedComposerState : transaction.states) {
+            const auto& clientState = resolvedComposerState.state;
+            uint32_t layerId = LayerHandle::getLayerId(clientState.surface);
+            if (layerId == UNASSIGNED_LAYER_ID) {
+                ALOGW("%s Handle %p is not valid", __func__, clientState.surface.get());
+                continue;
+            }
+
+            RequestedLayerState* layer = getLayerFromId(layerId);
+            if (layer == nullptr) {
+                LOG_ALWAYS_FATAL("%s Layer with handle %p (layerid=%d) not found", __func__,
+                                 clientState.surface.get(), layerId);
+                continue;
+            }
+
+            if (!layer->handleAlive) {
+                LOG_ALWAYS_FATAL("%s Layer's handle %p (layerid=%d) is not alive. Possible out of "
+                                 "order LayerLifecycleManager updates",
+                                 __func__, clientState.surface.get(), layerId);
+                continue;
+            }
+
+            uint32_t oldParentId = layer->parentId;
+            uint32_t oldRelativeParentId = layer->relativeParentId;
+            uint32_t oldTouchCropId = layer->touchCropId;
+            layer->merge(resolvedComposerState);
+
+            if (layer->what & layer_state_t::eBackgroundColorChanged) {
+                if (layer->bgColorLayerId == UNASSIGNED_LAYER_ID && layer->bgColorAlpha != 0) {
+                    LayerCreationArgs backgroundLayerArgs{nullptr,
+                                                          nullptr,
+                                                          layer->name + "BackgroundColorLayer",
+                                                          ISurfaceComposerClient::eFXSurfaceEffect,
+                                                          {}};
+                    std::vector<std::unique_ptr<RequestedLayerState>> newLayers;
+                    newLayers.emplace_back(
+                            std::make_unique<RequestedLayerState>(backgroundLayerArgs));
+                    RequestedLayerState* backgroundLayer = newLayers.back().get();
+                    backgroundLayer->handleAlive = false;
+                    backgroundLayer->parentId = layer->id;
+                    backgroundLayer->z = std::numeric_limits<int32_t>::min();
+                    backgroundLayer->color.rgb = layer->color.rgb;
+                    backgroundLayer->color.a = layer->bgColorAlpha;
+                    backgroundLayer->dataspace = layer->bgColorDataspace;
+
+                    layer->bgColorLayerId = backgroundLayer->id;
+                    addLayers({std::move(newLayers)});
+                } else if (layer->bgColorLayerId != UNASSIGNED_LAYER_ID &&
+                           layer->bgColorAlpha == 0) {
+                    RequestedLayerState* bgColorLayer = getLayerFromId(layer->bgColorLayerId);
+                    bgColorLayer->parentId = UNASSIGNED_LAYER_ID;
+                    onHandlesDestroyed({layer->bgColorLayerId});
+                } else if (layer->bgColorLayerId != UNASSIGNED_LAYER_ID) {
+                    RequestedLayerState* bgColorLayer = getLayerFromId(layer->bgColorLayerId);
+                    bgColorLayer->color.rgb = layer->color.rgb;
+                    bgColorLayer->color.a = layer->bgColorAlpha;
+                    bgColorLayer->dataspace = layer->bgColorDataspace;
+                    mGlobalChanges |= RequestedLayerState::Changes::Content;
+                }
+            }
+
+            if (oldParentId != layer->parentId) {
+                unlinkLayer(oldParentId, layer->id);
+                linkLayer(layer->parentId, layer->id);
+            }
+            if (oldRelativeParentId != layer->relativeParentId) {
+                unlinkLayer(oldRelativeParentId, layer->id);
+                linkLayer(layer->relativeParentId, layer->id);
+            }
+            if (oldTouchCropId != layer->touchCropId) {
+                unlinkLayer(oldTouchCropId, layer->id);
+                linkLayer(layer->touchCropId, layer->id);
+            }
+
+            mGlobalChanges |= layer->changes &
+                    (RequestedLayerState::Changes::Hierarchy |
+                     RequestedLayerState::Changes::Geometry |
+                     RequestedLayerState::Changes::Content);
+        }
+    }
+}
+
+void LayerLifecycleManager::commitChanges() {
+    for (auto& layer : mLayers) {
+        if (layer->changes.test(RequestedLayerState::Changes::Created)) {
+            for (auto listener : mListeners) {
+                listener->onLayerAdded(*layer);
+            }
+        }
+        layer->what = 0;
+        layer->changes.clear();
+    }
+
+    for (auto& destroyedLayer : mDestroyedLayers) {
+        if (destroyedLayer->changes.test(RequestedLayerState::Changes::Created)) {
+            for (auto listener : mListeners) {
+                listener->onLayerAdded(*destroyedLayer);
+            }
+        }
+
+        for (auto listener : mListeners) {
+            listener->onLayerDestroyed(*destroyedLayer);
+        }
+    }
+    mDestroyedLayers.clear();
+    mGlobalChanges.clear();
+}
+
+void LayerLifecycleManager::addLifecycleListener(std::shared_ptr<ILifecycleListener> listener) {
+    mListeners.emplace_back(std::move(listener));
+}
+
+void LayerLifecycleManager::removeLifecycleListener(std::shared_ptr<ILifecycleListener> listener) {
+    swapErase(mListeners, listener);
+}
+
+const std::vector<std::unique_ptr<RequestedLayerState>>& LayerLifecycleManager::getLayers() const {
+    return mLayers;
+}
+
+const std::vector<std::unique_ptr<RequestedLayerState>>& LayerLifecycleManager::getDestroyedLayers()
+        const {
+    return mDestroyedLayers;
+}
+
+const ftl::Flags<RequestedLayerState::Changes> LayerLifecycleManager::getGlobalChanges() const {
+    return mGlobalChanges;
+}
+
+RequestedLayerState* LayerLifecycleManager::getLayerFromId(uint32_t id) {
+    if (id == UNASSIGNED_LAYER_ID) {
+        return nullptr;
+    }
+    auto it = mIdToLayer.find(id);
+    if (it == mIdToLayer.end()) {
+        return nullptr;
+    }
+    return &it->second.owner;
+}
+
+std::vector<uint32_t>* LayerLifecycleManager::getLinkedLayersFromId(uint32_t id) {
+    if (id == UNASSIGNED_LAYER_ID) {
+        return nullptr;
+    }
+    auto it = mIdToLayer.find(id);
+    if (it == mIdToLayer.end()) {
+        return nullptr;
+    }
+    return &it->second.references;
+}
+
+void LayerLifecycleManager::linkLayer(uint32_t layerId, uint32_t layerToLink) {
+    if (layerToLink && layerId != UNASSIGNED_LAYER_ID) {
+        std::vector<uint32_t>* linkedLayers = getLinkedLayersFromId(layerId);
+        if (!linkedLayers) {
+            LOG_ALWAYS_FATAL("Could not find layer id %d to link %d", layerId, layerToLink);
+            return;
+        }
+        linkedLayers->emplace_back(layerToLink);
+    }
+}
+
+void LayerLifecycleManager::unlinkLayer(uint32_t& inOutLayerId, uint32_t linkedLayer) {
+    uint32_t layerId = inOutLayerId;
+    inOutLayerId = UNASSIGNED_LAYER_ID;
+
+    std::vector<uint32_t>* linkedLayers = getLinkedLayersFromId(layerId);
+    if (!linkedLayers) {
+        return;
+    }
+    swapErase(*linkedLayers, linkedLayer);
+}
+
+std::string LayerLifecycleManager::References::getDebugString() const {
+    std::string debugInfo = owner.name + "[" + std::to_string(owner.id) + "] refs:";
+    std::for_each(references.begin(), references.end(),
+                  [&debugInfo = debugInfo](const uint32_t& reference) mutable {
+                      debugInfo += std::to_string(reference) + ",";
+                  });
+    return debugInfo;
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
new file mode 100644
index 0000000..ad70d3f
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "RequestedLayerState.h"
+#include "TransactionState.h"
+
+namespace android::surfaceflinger::frontend {
+
+// Owns a collection of RequestedLayerStates and manages their lifecycle
+// and state changes.
+//
+// RequestedLayerStates are tracked and destroyed if they have no parent and
+// no handle left to keep them alive. The handle does not keep a reference to
+// the RequestedLayerState but a layer id associated with the RequestedLayerState.
+// If the handle is destroyed and the RequestedLayerState does not have a parent,
+// the LayerLifecycleManager destroys the RequestedLayerState.
+//
+// Threading: This class is not thread safe, it requires external synchronization.
+//
+// Typical usage: Input states (new layers, transactions, destroyed layer handles)
+// are collected in the background passed into the LayerLifecycleManager to update
+// layer lifecycle and layer state at start of composition.
+class LayerLifecycleManager {
+public:
+    // External state changes should be updated in the following order:
+    void addLayers(std::vector<std::unique_ptr<RequestedLayerState>>);
+    void applyTransactions(const std::vector<TransactionState>&);
+    void onHandlesDestroyed(const std::vector<uint32_t>&);
+
+    // Destroys RequestedLayerStates that are marked to be destroyed. Invokes all
+    // ILifecycleListener callbacks and clears any change flags from previous state
+    // updates. This function should be called outside the hot path since it's not
+    // critical to composition.
+    void commitChanges();
+
+    class ILifecycleListener {
+    public:
+        virtual ~ILifecycleListener() = default;
+        // Called on commitChanges when a layer is added. The callback includes
+        // the layer state the client was created with as well as any state updates
+        // until changes were committed.
+        virtual void onLayerAdded(const RequestedLayerState&) = 0;
+        // Called on commitChanges when a layer has been destroyed. The callback
+        // includes the final state before the layer was destroyed.
+        virtual void onLayerDestroyed(const RequestedLayerState&) = 0;
+    };
+    void addLifecycleListener(std::shared_ptr<ILifecycleListener>);
+    void removeLifecycleListener(std::shared_ptr<ILifecycleListener>);
+    const std::vector<std::unique_ptr<RequestedLayerState>>& getLayers() const;
+    const std::vector<std::unique_ptr<RequestedLayerState>>& getDestroyedLayers() const;
+    const ftl::Flags<RequestedLayerState::Changes> getGlobalChanges() const;
+
+private:
+    friend class LayerLifecycleManagerTest;
+    friend class HierarchyBuilderTest;
+    friend class android::SurfaceFlinger;
+
+    RequestedLayerState* getLayerFromId(uint32_t);
+    std::vector<uint32_t>* getLinkedLayersFromId(uint32_t);
+    void linkLayer(uint32_t layerId, uint32_t layerToLink);
+    void unlinkLayer(uint32_t& inOutLayerId, uint32_t linkedLayer);
+
+    struct References {
+        // Lifetime tied to mLayers
+        RequestedLayerState& owner;
+        std::vector<uint32_t> references;
+        std::string getDebugString() const;
+    };
+    std::unordered_map<uint32_t, References> mIdToLayer;
+    // Listeners are invoked once changes are committed.
+    std::vector<std::shared_ptr<ILifecycleListener>> mListeners;
+
+    // Aggregation of changes since last commit.
+    ftl::Flags<RequestedLayerState::Changes> mGlobalChanges;
+    std::vector<std::unique_ptr<RequestedLayerState>> mLayers;
+    // Layers pending destruction. Layers will be destroyed once changes are committed.
+    std::vector<std::unique_ptr<RequestedLayerState>> mDestroyedLayers;
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
new file mode 100644
index 0000000..45058d9
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -0,0 +1,358 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "FrontEnd/LayerCreationArgs.h"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#undef LOG_TAG
+#define LOG_TAG "RequestedLayerState"
+
+#include <private/android_filesystem_config.h>
+#include <sys/types.h>
+
+#include "Layer.h"
+#include "LayerHandle.h"
+#include "RequestedLayerState.h"
+
+namespace android::surfaceflinger::frontend {
+using ftl::Flags;
+using namespace ftl::flag_operators;
+
+namespace {
+uint32_t getLayerIdFromSurfaceControl(sp<SurfaceControl> surfaceControl) {
+    if (!surfaceControl) {
+        return UNASSIGNED_LAYER_ID;
+    }
+
+    return LayerHandle::getLayerId(surfaceControl->getHandle());
+}
+
+std::string layerIdToString(uint32_t layerId) {
+    return layerId == UNASSIGNED_LAYER_ID ? std::to_string(layerId) : "none";
+}
+
+} // namespace
+
+RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args)
+      : id(args.sequence),
+        name(args.name),
+        canBeRoot(args.addToRoot),
+        layerCreationFlags(args.flags),
+        textureName(args.textureName),
+        ownerUid(args.ownerUid),
+        ownerPid(args.ownerPid) {
+    layerId = static_cast<int32_t>(args.sequence);
+    changes |= RequestedLayerState::Changes::Created;
+    metadata.merge(args.metadata);
+    changes |= RequestedLayerState::Changes::Metadata;
+    handleAlive = true;
+    parentId = LayerHandle::getLayerId(args.parentHandle.promote());
+    mirrorId = LayerHandle::getLayerId(args.mirrorLayerHandle.promote());
+    if (mirrorId != UNASSIGNED_LAYER_ID) {
+        changes |= RequestedLayerState::Changes::Mirror;
+    }
+
+    flags = 0;
+    if (args.flags & ISurfaceComposerClient::eHidden) flags |= layer_state_t::eLayerHidden;
+    if (args.flags & ISurfaceComposerClient::eOpaque) flags |= layer_state_t::eLayerOpaque;
+    if (args.flags & ISurfaceComposerClient::eSecure) flags |= layer_state_t::eLayerSecure;
+    if (args.flags & ISurfaceComposerClient::eSkipScreenshot) {
+        flags |= layer_state_t::eLayerSkipScreenshot;
+    }
+    premultipliedAlpha = !(args.flags & ISurfaceComposerClient::eNonPremultiplied);
+    potentialCursor = args.flags & ISurfaceComposerClient::eCursorWindow;
+    protectedByApp = args.flags & ISurfaceComposerClient::eProtectedByApp;
+    if (args.flags & ISurfaceComposerClient::eNoColorFill) {
+        // Set an invalid color so there is no color fill.
+        // (b/259981098) use an explicit flag instead of relying on invalid values.
+        color.r = -1.0_hf;
+        color.g = -1.0_hf;
+        color.b = -1.0_hf;
+    } else {
+        color.rgb = {0.0_hf, 0.0_hf, 0.0_hf};
+    }
+    color.a = 1.0f;
+
+    crop.makeInvalid();
+    z = 0;
+    layerStack = ui::DEFAULT_LAYER_STACK;
+    transformToDisplayInverse = false;
+    dataspace = ui::Dataspace::UNKNOWN;
+    dataspaceRequested = false;
+    hdrMetadata.validTypes = 0;
+    surfaceDamageRegion = Region::INVALID_REGION;
+    cornerRadius = 0.0f;
+    backgroundBlurRadius = 0;
+    api = -1;
+    hasColorTransform = false;
+    bufferTransform = 0;
+    requestedTransform.reset();
+    bufferData = std::make_shared<BufferData>();
+    bufferData->frameNumber = 0;
+    bufferData->acquireFence = sp<Fence>::make(-1);
+    acquireFenceTime = std::make_shared<FenceTime>(bufferData->acquireFence);
+    colorSpaceAgnostic = false;
+    frameRateSelectionPriority = Layer::PRIORITY_UNSET;
+    shadowRadius = 0.f;
+    fixedTransformHint = ui::Transform::ROT_INVALID;
+    destinationFrame.makeInvalid();
+    isTrustedOverlay = false;
+    dropInputMode = gui::DropInputMode::NONE;
+    dimmingEnabled = true;
+    defaultFrameRateCompatibility =
+            static_cast<int8_t>(scheduler::LayerInfo::FrameRateCompatibility::Default);
+    dataspace = ui::Dataspace::V0_SRGB;
+}
+
+void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerState) {
+    bool oldFlags = flags;
+    Rect oldBufferSize = getBufferSize();
+    const layer_state_t& clientState = resolvedComposerState.state;
+
+    uint64_t clientChanges = what | layer_state_t::diff(clientState);
+    layer_state_t::merge(clientState);
+    what = clientChanges;
+
+    if (clientState.what & layer_state_t::eFlagsChanged) {
+        if ((oldFlags ^ flags) & layer_state_t::eLayerHidden) {
+            changes |= RequestedLayerState::Changes::Visibility;
+        }
+        if ((oldFlags ^ flags) & layer_state_t::eIgnoreDestinationFrame) {
+            changes |= RequestedLayerState::Changes::Geometry;
+        }
+    }
+    if (clientState.what & layer_state_t::eBufferChanged && oldBufferSize != getBufferSize()) {
+        changes |= RequestedLayerState::Changes::Geometry;
+    }
+    if (clientChanges & layer_state_t::HIERARCHY_CHANGES)
+        changes |= RequestedLayerState::Changes::Hierarchy;
+    if (clientChanges & layer_state_t::CONTENT_CHANGES)
+        changes |= RequestedLayerState::Changes::Content;
+    if (clientChanges & layer_state_t::GEOMETRY_CHANGES)
+        changes |= RequestedLayerState::Changes::Geometry;
+
+    if (clientState.what & layer_state_t::eColorTransformChanged) {
+        static const mat4 identityMatrix = mat4();
+        hasColorTransform = colorTransform != identityMatrix;
+    }
+    if (clientState.what & layer_state_t::eLayerChanged) {
+        changes |= RequestedLayerState::Changes::Z;
+    }
+    if (clientState.what & layer_state_t::eReparent) {
+        changes |= RequestedLayerState::Changes::Parent;
+        parentId = getLayerIdFromSurfaceControl(clientState.parentSurfaceControlForChild);
+        parentSurfaceControlForChild = nullptr;
+    }
+    if (clientState.what & layer_state_t::eRelativeLayerChanged) {
+        changes |= RequestedLayerState::Changes::RelativeParent;
+        relativeParentId = getLayerIdFromSurfaceControl(clientState.relativeLayerSurfaceControl);
+        isRelativeOf = true;
+        relativeLayerSurfaceControl = nullptr;
+    }
+    if ((clientState.what & layer_state_t::eLayerChanged ||
+         (clientState.what & layer_state_t::eReparent && parentId == UNASSIGNED_LAYER_ID)) &&
+        isRelativeOf) {
+        // clear out relz data
+        relativeParentId = UNASSIGNED_LAYER_ID;
+        isRelativeOf = false;
+        changes |= RequestedLayerState::Changes::RelativeParent;
+    }
+    if (clientState.what & layer_state_t::eReparent && parentId == relativeParentId) {
+        // provide a hint that we are are now a direct child and not a relative child.
+        changes |= RequestedLayerState::Changes::RelativeParent;
+    }
+    if (clientState.what & layer_state_t::eInputInfoChanged) {
+        wp<IBinder>& touchableRegionCropHandle =
+                windowInfoHandle->editInfo()->touchableRegionCropHandle;
+        touchCropId = LayerHandle::getLayerId(touchableRegionCropHandle.promote());
+        changes |= RequestedLayerState::Changes::Input;
+        touchableRegionCropHandle.clear();
+    }
+    if (clientState.what & layer_state_t::eStretchChanged) {
+        stretchEffect.sanitize();
+    }
+
+    if (clientState.what & layer_state_t::eHasListenerCallbacksChanged) {
+        // TODO(b/238781169) handle callbacks
+    }
+
+    if (clientState.what & layer_state_t::eBufferChanged) {
+        externalTexture = resolvedComposerState.externalTexture;
+        hwcBufferSlot = resolvedComposerState.hwcBufferSlot;
+    }
+
+    if (clientState.what & layer_state_t::ePositionChanged) {
+        requestedTransform.set(x, y);
+    }
+
+    if (clientState.what & layer_state_t::eMatrixChanged) {
+        requestedTransform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy);
+    }
+}
+
+ui::Transform RequestedLayerState::getTransform() const {
+    if ((flags & layer_state_t::eIgnoreDestinationFrame) || destinationFrame.isEmpty()) {
+        // If destination frame is not set, use the requested transform set via
+        // Transaction::setPosition and Transaction::setMatrix.
+        return requestedTransform;
+    }
+
+    Rect destRect = destinationFrame;
+    int32_t destW = destRect.width();
+    int32_t destH = destRect.height();
+    if (destRect.left < 0) {
+        destRect.left = 0;
+        destRect.right = destW;
+    }
+    if (destRect.top < 0) {
+        destRect.top = 0;
+        destRect.bottom = destH;
+    }
+
+    if (!externalTexture) {
+        ui::Transform transform;
+        transform.set(static_cast<float>(destRect.left), static_cast<float>(destRect.top));
+        return transform;
+    }
+
+    uint32_t bufferWidth = externalTexture->getWidth();
+    uint32_t bufferHeight = externalTexture->getHeight();
+    // Undo any transformations on the buffer.
+    if (bufferTransform & ui::Transform::ROT_90) {
+        std::swap(bufferWidth, bufferHeight);
+    }
+    // TODO(b/238781169) remove dep
+    uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags();
+    if (transformToDisplayInverse) {
+        if (invTransform & ui::Transform::ROT_90) {
+            std::swap(bufferWidth, bufferHeight);
+        }
+    }
+
+    float sx = static_cast<float>(destW) / static_cast<float>(bufferWidth);
+    float sy = static_cast<float>(destH) / static_cast<float>(bufferHeight);
+    ui::Transform transform;
+    transform.set(sx, 0, 0, sy);
+    transform.set(static_cast<float>(destRect.left), static_cast<float>(destRect.top));
+    return transform;
+}
+
+std::string RequestedLayerState::getDebugString() const {
+    return "[" + std::to_string(id) + "]" + name + ",parent=" + layerIdToString(parentId) +
+            ",relativeParent=" + layerIdToString(relativeParentId) +
+            ",isRelativeOf=" + std::to_string(isRelativeOf) +
+            ",mirrorId=" + layerIdToString(mirrorId) +
+            ",handleAlive=" + std::to_string(handleAlive);
+}
+
+std::string RequestedLayerState::getDebugStringShort() const {
+    return "[" + std::to_string(id) + "]" + name;
+}
+
+bool RequestedLayerState::canBeDestroyed() const {
+    return !handleAlive && parentId == UNASSIGNED_LAYER_ID;
+}
+bool RequestedLayerState::isRoot() const {
+    return canBeRoot && parentId == UNASSIGNED_LAYER_ID;
+}
+bool RequestedLayerState::isHiddenByPolicy() const {
+    return (flags & layer_state_t::eLayerHidden) == layer_state_t::eLayerHidden;
+};
+half4 RequestedLayerState::getColor() const {
+    if ((sidebandStream != nullptr) || (externalTexture != nullptr)) {
+        return {0._hf, 0._hf, 0._hf, color.a};
+    }
+    return color;
+}
+Rect RequestedLayerState::getBufferSize() const {
+    // for buffer state layers we use the display frame size as the buffer size.
+    if (!externalTexture) {
+        return Rect::INVALID_RECT;
+    }
+
+    uint32_t bufWidth = externalTexture->getWidth();
+    uint32_t bufHeight = externalTexture->getHeight();
+
+    // Undo any transformations on the buffer and return the result.
+    if (bufferTransform & ui::Transform::ROT_90) {
+        std::swap(bufWidth, bufHeight);
+    }
+
+    if (transformToDisplayInverse) {
+        // TODO(b/238781169) pass in display metrics (would be useful for input info as well
+        uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags();
+        if (invTransform & ui::Transform::ROT_90) {
+            std::swap(bufWidth, bufHeight);
+        }
+    }
+
+    return Rect(0, 0, static_cast<int32_t>(bufWidth), static_cast<int32_t>(bufHeight));
+}
+
+Rect RequestedLayerState::getCroppedBufferSize() const {
+    Rect size = getBufferSize();
+    if (!crop.isEmpty() && size.isValid()) {
+        size.intersect(crop, &size);
+    } else if (!crop.isEmpty()) {
+        size = crop;
+    }
+    return size;
+}
+
+Rect RequestedLayerState::getBufferCrop() const {
+    // this is the crop rectangle that applies to the buffer
+    // itself (as opposed to the window)
+    if (!bufferCrop.isEmpty()) {
+        // if the buffer crop is defined, we use that
+        return bufferCrop;
+    } else if (externalTexture != nullptr) {
+        // otherwise we use the whole buffer
+        return externalTexture->getBounds();
+    } else {
+        // if we don't have a buffer yet, we use an empty/invalid crop
+        return Rect();
+    }
+}
+
+aidl::android::hardware::graphics::composer3::Composition RequestedLayerState::getCompositionType()
+        const {
+    using aidl::android::hardware::graphics::composer3::Composition;
+    // TODO(b/238781169) check about sidestream ready flag
+    if (sidebandStream.get()) {
+        return Composition::SIDEBAND;
+    }
+    if (!externalTexture) {
+        return Composition::SOLID_COLOR;
+    }
+    if (flags & layer_state_t::eLayerIsDisplayDecoration) {
+        return Composition::DISPLAY_DECORATION;
+    }
+    if (potentialCursor) {
+        return Composition::CURSOR;
+    }
+    return Composition::DEVICE;
+}
+
+Rect RequestedLayerState::reduce(const Rect& win, const Region& exclude) {
+    if (CC_LIKELY(exclude.isEmpty())) {
+        return win;
+    }
+    if (exclude.isRect()) {
+        return win.reduce(exclude.getBounds());
+    }
+    return Region(win).subtract(exclude).getBounds();
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
new file mode 100644
index 0000000..0ddf5e2
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/graphics/composer3/Composition.h>
+#include <ftl/flags.h>
+#include <gui/LayerState.h>
+#include <renderengine/ExternalTexture.h>
+
+#include "LayerCreationArgs.h"
+#include "TransactionState.h"
+
+namespace android::surfaceflinger::frontend {
+
+// Stores client requested states for a layer.
+// This struct does not store any other states or states pertaining to
+// other layers. Links to other layers that are part of the client
+// requested state such as parent are translated to layer id so
+// we can avoid extending the lifetime of layer handles.
+struct RequestedLayerState : layer_state_t {
+    // Changes in state after merging with new state. This includes additional state
+    // changes found in layer_state_t::what.
+    enum class Changes : uint32_t {
+        Created = 1u << 0,
+        Destroyed = 1u << 1,
+        Hierarchy = 1u << 2,
+        Geometry = 1u << 3,
+        Content = 1u << 4,
+        Input = 1u << 5,
+        Z = 1u << 6,
+        Mirror = 1u << 7,
+        Parent = 1u << 8,
+        RelativeParent = 1u << 9,
+        Metadata = 1u << 10,
+        Visibility = 1u << 11,
+    };
+    static Rect reduce(const Rect& win, const Region& exclude);
+    RequestedLayerState(const LayerCreationArgs&);
+    void merge(const ResolvedComposerState&);
+    ui::Transform getTransform() const;
+    bool canBeDestroyed() const;
+    bool isRoot() const;
+    bool isHiddenByPolicy() const;
+    half4 getColor() const;
+    Rect getBufferSize() const;
+    Rect getCroppedBufferSize() const;
+    Rect getBufferCrop() const;
+    std::string getDebugString() const;
+    std::string getDebugStringShort() const;
+    aidl::android::hardware::graphics::composer3::Composition getCompositionType() const;
+
+    // Layer serial number.  This gives layers an explicit ordering, so we
+    // have a stable sort order when their layer stack and Z-order are
+    // the same.
+    const uint32_t id;
+    const std::string name;
+    const bool canBeRoot = false;
+    const uint32_t layerCreationFlags;
+    const uint32_t textureName;
+    // The owner of the layer. If created from a non system process, it will be the calling uid.
+    // If created from a system process, the value can be passed in.
+    const uid_t ownerUid;
+    // The owner pid of the layer. If created from a non system process, it will be the calling pid.
+    // If created from a system process, the value can be passed in.
+    const pid_t ownerPid;
+    bool dataspaceRequested;
+    bool hasColorTransform;
+    bool premultipliedAlpha{true};
+    // This layer can be a cursor on some displays.
+    bool potentialCursor{false};
+    bool protectedByApp{false}; // application requires protected path to external sink
+    ui::Transform requestedTransform;
+    std::shared_ptr<FenceTime> acquireFenceTime;
+    std::shared_ptr<renderengine::ExternalTexture> externalTexture;
+    int hwcBufferSlot = 0;
+
+    // book keeping states
+    bool handleAlive = true;
+    bool isRelativeOf = false;
+    uint32_t parentId = UNASSIGNED_LAYER_ID;
+    uint32_t relativeParentId = UNASSIGNED_LAYER_ID;
+    uint32_t mirrorId = UNASSIGNED_LAYER_ID;
+    uint32_t touchCropId = UNASSIGNED_LAYER_ID;
+    uint32_t bgColorLayerId = UNASSIGNED_LAYER_ID;
+    ftl::Flags<RequestedLayerState::Changes> changes;
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/SwapErase.h b/services/surfaceflinger/FrontEnd/SwapErase.h
new file mode 100644
index 0000000..f672f99
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/SwapErase.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <vector>
+
+namespace android::surfaceflinger::frontend {
+// Erases the first element in vec that matches value. This is a more optimal way to
+// remove an element from a vector that avoids relocating all the elements after the one
+// that is erased.
+template <typename T>
+void swapErase(std::vector<T>& vec, const T& value) {
+    auto it = std::find(vec.begin(), vec.end(), value);
+    if (it != vec.end()) {
+        std::iter_swap(it, vec.end() - 1);
+        vec.erase(vec.end() - 1);
+    }
+}
+
+// Similar to swapErase(std::vector<T>& vec, const T& value) but erases the first element
+// that returns true for predicate.
+template <typename T, class P>
+void swapErase(std::vector<T>& vec, P predicate) {
+    auto it = std::find_if(vec.begin(), vec.end(), predicate);
+    if (it != vec.end()) {
+        std::iter_swap(it, vec.end() - 1);
+        vec.erase(vec.end() - 1);
+    }
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
index 95961fe..8629671 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
@@ -24,7 +24,7 @@
 
 #include "TransactionHandler.h"
 
-namespace android {
+namespace android::surfaceflinger::frontend {
 
 void TransactionHandler::queueTransaction(TransactionState&& state) {
     mLocklessTransactionQueue.push(std::move(state));
@@ -186,4 +186,4 @@
         mStalledTransactions.erase(it);
     }
 }
-} // namespace android
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.h b/services/surfaceflinger/FrontEnd/TransactionHandler.h
index 2b6f07d..a06b870 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.h
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.h
@@ -18,9 +18,6 @@
 
 #include <semaphore.h>
 #include <cstdint>
-#include <mutex>
-#include <queue>
-#include <thread>
 #include <vector>
 
 #include <LocklessQueue.h>
@@ -30,6 +27,10 @@
 #include <ftl/small_vector.h>
 
 namespace android {
+
+class TestableSurfaceFlinger;
+namespace surfaceflinger::frontend {
+
 class TransactionHandler {
 public:
     struct TransactionFlushState {
@@ -60,7 +61,7 @@
 
 private:
     // For unit tests
-    friend class TestableSurfaceFlinger;
+    friend class ::android::TestableSurfaceFlinger;
 
     int flushPendingTransactionQueues(std::vector<TransactionState>&, TransactionFlushState&);
     TransactionReadiness applyFilters(TransactionFlushState&);
@@ -71,5 +72,5 @@
     ftl::SmallVector<TransactionFilter, 2> mTransactionReadyFilters;
     std::vector<uint64_t> mStalledTransactions;
 };
-
+} // namespace surfaceflinger::frontend
 } // namespace android
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 8a3e784..d53f0b1 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -1123,7 +1123,7 @@
 
     // We return whether this layer ot its children has a vote. We ignore ExactOrMultiple votes for
     // the same reason we are allowing touch boost for those layers. See
-    // RefreshRateSelector::rankRefreshRates for details.
+    // RefreshRateSelector::rankFrameRates for details.
     const auto layerVotedWithDefaultCompatibility =
             frameRate.rate.isValid() && frameRate.type == FrameRateCompatibility::Default;
     const auto layerVotedWithNoVote = frameRate.type == FrameRateCompatibility::NoVote;
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 7669bab..f743896 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -83,6 +83,7 @@
 } // namespace frametimeline
 
 class Layer : public virtual RefBase {
+public:
     // The following constants represent priority of the window. SF uses this information when
     // deciding which window has a priority when deciding about the refresh rate of the screen.
     // Priority 0 is considered the highest priority. -1 means that the priority is unset.
@@ -94,7 +95,6 @@
     // Windows that are not in focus, but voted for a specific mode ID.
     static constexpr int32_t PRIORITY_NOT_FOCUSED_WITH_MODE = 2;
 
-public:
     enum { // flags for doTransaction()
         eDontUpdateGeometryState = 0x00000001,
         eVisibleRegion = 0x00000002,
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 8d4ea05..fd1a733 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -24,6 +24,7 @@
 #include <chrono>
 #include <cmath>
 #include <deque>
+#include <map>
 
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
@@ -31,6 +32,7 @@
 #include <ftl/fake_guard.h>
 #include <ftl/match.h>
 #include <ftl/unit.h>
+#include <scheduler/FrameRateMode.h>
 #include <utils/Trace.h>
 
 #include "../SurfaceFlingerProperties.h"
@@ -43,7 +45,7 @@
 namespace {
 
 struct RefreshRateScore {
-    DisplayModeIterator modeIt;
+    FrameRateMode frameRateMode;
     float overallScore;
     struct {
         float modeBelowThreshold;
@@ -77,19 +79,11 @@
     return knownFrameRates;
 }
 
-// The Filter is a `bool(const DisplayMode&)` predicate.
-template <typename Filter>
-std::vector<DisplayModeIterator> sortByRefreshRate(const DisplayModes& modes, Filter&& filter) {
+std::vector<DisplayModeIterator> sortByRefreshRate(const DisplayModes& modes) {
     std::vector<DisplayModeIterator> sortedModes;
     sortedModes.reserve(modes.size());
-
     for (auto it = modes.begin(); it != modes.end(); ++it) {
-        const auto& [id, mode] = *it;
-
-        if (filter(*mode)) {
-            ALOGV("%s: including mode %d", __func__, id.value());
-            sortedModes.push_back(it);
-        }
+        sortedModes.push_back(it);
     }
 
     std::sort(sortedModes.begin(), sortedModes.end(), [](auto it1, auto it2) {
@@ -106,6 +100,21 @@
     return sortedModes;
 }
 
+std::pair<unsigned, unsigned> divisorRange(Fps fps, FpsRange range,
+                                           RefreshRateSelector::Config::FrameRateOverride config) {
+    if (config != RefreshRateSelector::Config::FrameRateOverride::Enabled) {
+        return {1, 1};
+    }
+
+    using fps_approx_ops::operator/;
+    const auto start = std::max(1u, fps / range.max - 1);
+    const auto end = fps /
+            std::max(range.min, RefreshRateSelector::kMinSupportedFrameRate,
+                     fps_approx_ops::operator<);
+
+    return {start, end};
+}
+
 bool shouldEnableFrameRateOverride(const std::vector<DisplayModeIterator>& sortedModes) {
     for (const auto it1 : sortedModes) {
         const auto& mode1 = it1->second;
@@ -136,27 +145,109 @@
 
 } // namespace
 
+auto RefreshRateSelector::createFrameRateModes(
+        std::function<bool(const DisplayMode&)>&& filterModes, const FpsRange& renderRange) const
+        -> std::vector<FrameRateMode> {
+    struct Key {
+        Fps fps;
+        int32_t group;
+    };
+
+    struct KeyLess {
+        bool operator()(const Key& a, const Key& b) const {
+            using namespace fps_approx_ops;
+            if (a.fps != b.fps) {
+                return a.fps < b.fps;
+            }
+
+            // For the same fps the order doesn't really matter, but we still
+            // want the behaviour of a strictly less operator.
+            // We use the group id as the secondary ordering for that.
+            return a.group < b.group;
+        }
+    };
+
+    std::map<Key, DisplayModeIterator, KeyLess> ratesMap;
+    for (auto it = mDisplayModes.begin(); it != mDisplayModes.end(); ++it) {
+        const auto& [id, mode] = *it;
+
+        if (!filterModes(*mode)) {
+            continue;
+        }
+        const auto [start, end] =
+                divisorRange(mode->getFps(), renderRange, mConfig.enableFrameRateOverride);
+        for (auto divisor = start; divisor <= end; divisor++) {
+            const auto fps = mode->getFps() / divisor;
+            using fps_approx_ops::operator<;
+            if (fps < kMinSupportedFrameRate) {
+                break;
+            }
+
+            if (mConfig.enableFrameRateOverride == Config::FrameRateOverride::Enabled &&
+                !renderRange.includes(fps)) {
+                continue;
+            }
+
+            if (mConfig.enableFrameRateOverride ==
+                        Config::FrameRateOverride::AppOverrideNativeRefreshRates &&
+                !isNativeRefreshRate(fps)) {
+                continue;
+            }
+
+            const auto [existingIter, emplaceHappened] =
+                    ratesMap.try_emplace(Key{fps, mode->getGroup()}, it);
+            if (emplaceHappened) {
+                ALOGV("%s: including %s (%s)", __func__, to_string(fps).c_str(),
+                      to_string(mode->getFps()).c_str());
+            } else {
+                // We might need to update the map as we found a lower refresh rate
+                if (isStrictlyLess(mode->getFps(), existingIter->second->second->getFps())) {
+                    existingIter->second = it;
+                    ALOGV("%s: changing %s (%s)", __func__, to_string(fps).c_str(),
+                          to_string(mode->getFps()).c_str());
+                }
+            }
+        }
+    }
+
+    std::vector<FrameRateMode> frameRateModes;
+    frameRateModes.reserve(ratesMap.size());
+    for (const auto& [key, mode] : ratesMap) {
+        frameRateModes.emplace_back(FrameRateMode{key.fps, mode->second});
+    }
+
+    // We always want that the lowest frame rate will be corresponding to the
+    // lowest mode for power saving.
+    const auto lowestRefreshRateIt =
+            std::min_element(frameRateModes.begin(), frameRateModes.end(),
+                             [](const FrameRateMode& lhs, const FrameRateMode& rhs) {
+                                 return isStrictlyLess(lhs.modePtr->getFps(),
+                                                       rhs.modePtr->getFps());
+                             });
+    frameRateModes.erase(frameRateModes.begin(), lowestRefreshRateIt);
+
+    return frameRateModes;
+}
+
 struct RefreshRateSelector::RefreshRateScoreComparator {
     bool operator()(const RefreshRateScore& lhs, const RefreshRateScore& rhs) const {
-        const auto& [modeIt, overallScore, _] = lhs;
+        const auto& [frameRateMode, overallScore, _] = lhs;
 
-        std::string name = to_string(modeIt->second->getFps());
+        std::string name = to_string(frameRateMode);
+
         ALOGV("%s sorting scores %.2f", name.c_str(), overallScore);
-
         ATRACE_INT(name.c_str(), static_cast<int>(std::round(overallScore * 100)));
 
-        if (!ScoredRefreshRate::scoresEqual(overallScore, rhs.overallScore)) {
+        if (!ScoredFrameRate::scoresEqual(overallScore, rhs.overallScore)) {
             return overallScore > rhs.overallScore;
         }
 
-        // If overallScore tie we will pick the higher refresh rate if
-        // high refresh rate is the priority else the lower refresh rate.
         if (refreshRateOrder == RefreshRateOrder::Descending) {
             using fps_approx_ops::operator>;
-            return modeIt->second->getFps() > rhs.modeIt->second->getFps();
+            return frameRateMode.fps > rhs.frameRateMode.fps;
         } else {
             using fps_approx_ops::operator<;
-            return modeIt->second->getFps() < rhs.modeIt->second->getFps();
+            return frameRateMode.fps < rhs.frameRateMode.fps;
         }
     }
 
@@ -210,7 +301,15 @@
 
     if (layer.vote == LayerVoteType::ExplicitExactOrMultiple ||
         layer.vote == LayerVoteType::Heuristic) {
-        if (isFractionalPairOrMultiple(refreshRate, layer.desiredRefreshRate)) {
+        const float multiplier = refreshRate.getValue() / layer.desiredRefreshRate.getValue();
+
+        // We only want to score this layer as a fractional pair if the content is not
+        // significantly faster than the display rate, at it would cause a significant frame drop.
+        // It is more appropriate to choose a higher display rate even if
+        // a pull-down will be required.
+        constexpr float kMinMultiplier = 0.25f;
+        if (multiplier >= kMinMultiplier &&
+            isFractionalPairOrMultiple(refreshRate, layer.desiredRefreshRate)) {
             return kScoreForFractionalPairs;
         }
 
@@ -245,9 +344,9 @@
     return 0;
 }
 
-float RefreshRateSelector::calculateRefreshRateScoreForFps(Fps refreshRate) const {
-    const float ratio =
-            refreshRate.getValue() / mAppRequestRefreshRates.back()->second->getFps().getValue();
+float RefreshRateSelector::calculateDistanceScoreFromMax(Fps refreshRate) const {
+    const auto& maxFps = mAppRequestFrameRates.back().fps;
+    const float ratio = refreshRate.getValue() / maxFps.getValue();
     // Use ratio^2 to get a lower score the more we get further from peak
     return ratio * ratio;
 }
@@ -260,12 +359,12 @@
 
     // If the layer wants Max, give higher score to the higher refresh rate
     if (layer.vote == LayerVoteType::Max) {
-        return calculateRefreshRateScoreForFps(refreshRate);
+        return calculateDistanceScoreFromMax(refreshRate);
     }
 
     if (layer.vote == LayerVoteType::ExplicitExact) {
         const int divisor = getFrameRateDivisor(refreshRate, layer.desiredRefreshRate);
-        if (supportsFrameRateOverrideByContent()) {
+        if (supportsAppFrameRateOverrideByContent()) {
             // Since we support frame rate override, allow refresh rates which are
             // multiples of the layer's request, as those apps would be throttled
             // down to run at the desired refresh rate.
@@ -289,33 +388,33 @@
             kNonExactMatchingPenalty;
 }
 
-auto RefreshRateSelector::getRankedRefreshRates(const std::vector<LayerRequirement>& layers,
-                                                GlobalSignals signals) const -> RankedRefreshRates {
+auto RefreshRateSelector::getRankedFrameRates(const std::vector<LayerRequirement>& layers,
+                                              GlobalSignals signals) const -> RankedFrameRates {
     std::lock_guard lock(mLock);
 
-    if (mGetRankedRefreshRatesCache &&
-        mGetRankedRefreshRatesCache->arguments == std::make_pair(layers, signals)) {
-        return mGetRankedRefreshRatesCache->result;
+    if (mGetRankedFrameRatesCache &&
+        mGetRankedFrameRatesCache->arguments == std::make_pair(layers, signals)) {
+        return mGetRankedFrameRatesCache->result;
     }
 
-    const auto result = getRankedRefreshRatesLocked(layers, signals);
-    mGetRankedRefreshRatesCache = GetRankedRefreshRatesCache{{layers, signals}, result};
+    const auto result = getRankedFrameRatesLocked(layers, signals);
+    mGetRankedFrameRatesCache = GetRankedFrameRatesCache{{layers, signals}, result};
     return result;
 }
 
-auto RefreshRateSelector::getRankedRefreshRatesLocked(const std::vector<LayerRequirement>& layers,
-                                                      GlobalSignals signals) const
-        -> RankedRefreshRates {
+auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector<LayerRequirement>& layers,
+                                                    GlobalSignals signals) const
+        -> RankedFrameRates {
     using namespace fps_approx_ops;
     ATRACE_CALL();
     ALOGV("%s: %zu layers", __func__, layers.size());
 
     const auto& activeMode = *getActiveModeItLocked()->second;
 
-    // Keep the display at max refresh rate for the duration of powering on the display.
+    // Keep the display at max frame rate for the duration of powering on the display.
     if (signals.powerOnImminent) {
         ALOGV("Power On Imminent");
-        return {rankRefreshRates(activeMode.getGroup(), RefreshRateOrder::Descending),
+        return {rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Descending),
                 GlobalSignals{.powerOnImminent = true}};
     }
 
@@ -375,7 +474,7 @@
     // selected a refresh rate to see if we should apply touch boost.
     if (signals.touch && !hasExplicitVoteLayers) {
         ALOGV("Touch Boost");
-        return {rankRefreshRates(anchorGroup, RefreshRateOrder::Descending),
+        return {rankFrameRates(anchorGroup, RefreshRateOrder::Descending),
                 GlobalSignals{.touch = true}};
     }
 
@@ -387,27 +486,27 @@
 
     if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
         ALOGV("Idle");
-        return {rankRefreshRates(activeMode.getGroup(), RefreshRateOrder::Ascending),
+        return {rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending),
                 GlobalSignals{.idle = true}};
     }
 
     if (layers.empty() || noVoteLayers == layers.size()) {
         ALOGV("No layers with votes");
-        return {rankRefreshRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
+        return {rankFrameRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
     }
 
     // Only if all layers want Min we should return Min
     if (noVoteLayers + minVoteLayers == layers.size()) {
         ALOGV("All layers Min");
-        return {rankRefreshRates(activeMode.getGroup(), RefreshRateOrder::Ascending), kNoSignals};
+        return {rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending), kNoSignals};
     }
 
     // Find the best refresh rate based on score
     std::vector<RefreshRateScore> scores;
-    scores.reserve(mAppRequestRefreshRates.size());
+    scores.reserve(mAppRequestFrameRates.size());
 
-    for (const DisplayModeIterator modeIt : mAppRequestRefreshRates) {
-        scores.emplace_back(RefreshRateScore{modeIt, 0.0f});
+    for (const FrameRateMode& it : mAppRequestFrameRates) {
+        scores.emplace_back(RefreshRateScore{it, 0.0f});
     }
 
     for (const auto& layer : layers) {
@@ -420,13 +519,13 @@
 
         const auto weight = layer.weight;
 
-        for (auto& [modeIt, overallScore, fixedRateBelowThresholdLayersScore] : scores) {
-            const auto& [id, mode] = *modeIt;
-            const bool isSeamlessSwitch = mode->getGroup() == activeMode.getGroup();
+        for (auto& [mode, overallScore, fixedRateBelowThresholdLayersScore] : scores) {
+            const auto& [fps, modePtr] = mode;
+            const bool isSeamlessSwitch = modePtr->getGroup() == activeMode.getGroup();
 
             if (layer.seamlessness == Seamlessness::OnlySeamless && !isSeamlessSwitch) {
                 ALOGV("%s ignores %s to avoid non-seamless switch. Current mode = %s",
-                      formatLayerInfo(layer, weight).c_str(), to_string(*mode).c_str(),
+                      formatLayerInfo(layer, weight).c_str(), to_string(*modePtr).c_str(),
                       to_string(activeMode).c_str());
                 continue;
             }
@@ -435,7 +534,7 @@
                 !layer.focused) {
                 ALOGV("%s ignores %s because it's not focused and the switch is going to be seamed."
                       " Current mode = %s",
-                      formatLayerInfo(layer, weight).c_str(), to_string(*mode).c_str(),
+                      formatLayerInfo(layer, weight).c_str(), to_string(*modePtr).c_str(),
                       to_string(activeMode).c_str());
                 continue;
             }
@@ -445,14 +544,14 @@
             // 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 = mode->getGroup() == anchorGroup;
+            const bool isInPolicyForDefault = modePtr->getGroup() == anchorGroup;
             if (layer.seamlessness == Seamlessness::Default && !isInPolicyForDefault) {
                 ALOGV("%s ignores %s. Current mode = %s", formatLayerInfo(layer, weight).c_str(),
-                      to_string(*mode).c_str(), to_string(activeMode).c_str());
+                      to_string(*modePtr).c_str(), to_string(activeMode).c_str());
                 continue;
             }
 
-            const bool inPrimaryRange = policy->primaryRanges.physical.includes(mode->getFps());
+            const bool inPrimaryRange = policy->primaryRanges.physical.includes(modePtr->getFps());
             if ((primaryRangeIsSingleRate || !inPrimaryRange) &&
                 !(layer.focused &&
                   (layer.vote == LayerVoteType::ExplicitDefault ||
@@ -462,8 +561,7 @@
                 continue;
             }
 
-            const float layerScore =
-                    calculateLayerScoreLocked(layer, mode->getFps(), isSeamlessSwitch);
+            const float layerScore = calculateLayerScoreLocked(layer, fps, isSeamlessSwitch);
             const float weightedLayerScore = weight * layerScore;
 
             // Layer with fixed source has a special consideration which depends on the
@@ -491,21 +589,21 @@
                             Fps::fromValue(mConfig.frameRateMultipleThreshold / 2);
             if (fixedSourceLayer && layerBelowThreshold) {
                 const bool modeAboveThreshold =
-                        mode->getFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold);
+                        modePtr->getFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold);
                 if (modeAboveThreshold) {
-                    ALOGV("%s gives %s fixed source (above threshold) score of %.4f",
-                          formatLayerInfo(layer, weight).c_str(), to_string(mode->getFps()).c_str(),
-                          layerScore);
+                    ALOGV("%s gives %s (%s) fixed source (above threshold) score of %.4f",
+                          formatLayerInfo(layer, weight).c_str(), to_string(fps).c_str(),
+                          to_string(modePtr->getFps()).c_str(), layerScore);
                     fixedRateBelowThresholdLayersScore.modeAboveThreshold += weightedLayerScore;
                 } else {
-                    ALOGV("%s gives %s fixed source (below threshold) score of %.4f",
-                          formatLayerInfo(layer, weight).c_str(), to_string(mode->getFps()).c_str(),
-                          layerScore);
+                    ALOGV("%s gives %s (%s) fixed source (below threshold) score of %.4f",
+                          formatLayerInfo(layer, weight).c_str(), to_string(fps).c_str(),
+                          to_string(modePtr->getFps()).c_str(), layerScore);
                     fixedRateBelowThresholdLayersScore.modeBelowThreshold += weightedLayerScore;
                 }
             } else {
-                ALOGV("%s gives %s score of %.4f", formatLayerInfo(layer, weight).c_str(),
-                      to_string(mode->getFps()).c_str(), layerScore);
+                ALOGV("%s gives %s (%s) score of %.4f", formatLayerInfo(layer, weight).c_str(),
+                      to_string(fps).c_str(), to_string(modePtr->getFps()).c_str(), layerScore);
                 overallScore += weightedLayerScore;
             }
         }
@@ -524,25 +622,26 @@
         const auto maxScoreIt =
                 std::max_element(scores.begin(), scores.end(),
                                  [](RefreshRateScore max, RefreshRateScore current) {
-                                     const auto& [modeIt, overallScore, _] = current;
-                                     return overallScore > max.overallScore;
+                                     return current.overallScore > max.overallScore;
                                  });
-        ALOGV("%s is the best refresh rate without fixed source layers. It is %s the threshold for "
+        ALOGV("%s (%s) is the best refresh rate without fixed source layers. It is %s the "
+              "threshold for "
               "refresh rate multiples",
-              to_string(maxScoreIt->modeIt->second->getFps()).c_str(),
+              to_string(maxScoreIt->frameRateMode.fps).c_str(),
+              to_string(maxScoreIt->frameRateMode.modePtr->getFps()).c_str(),
               maxScoreAboveThreshold ? "above" : "below");
-        return maxScoreIt->modeIt->second->getFps() >=
+        return maxScoreIt->frameRateMode.modePtr->getFps() >=
                 Fps::fromValue(mConfig.frameRateMultipleThreshold);
     }();
 
     // Now we can add the fixed rate layers score
-    for (auto& [modeIt, overallScore, fixedRateBelowThresholdLayersScore] : scores) {
+    for (auto& [frameRateMode, overallScore, fixedRateBelowThresholdLayersScore] : scores) {
         overallScore += fixedRateBelowThresholdLayersScore.modeBelowThreshold;
         if (maxScoreAboveThreshold) {
             overallScore += fixedRateBelowThresholdLayersScore.modeAboveThreshold;
         }
-        ALOGV("%s adjusted overallScore is %.4f", to_string(modeIt->second->getFps()).c_str(),
-              overallScore);
+        ALOGV("%s (%s) adjusted overallScore is %.4f", to_string(frameRateMode.fps).c_str(),
+              to_string(frameRateMode.modePtr->getFps()).c_str(), overallScore);
     }
 
     // Now that we scored all the refresh rates we need to pick the one that got the highest
@@ -552,12 +651,12 @@
     std::sort(scores.begin(), scores.end(),
               RefreshRateScoreComparator{.refreshRateOrder = refreshRateOrder});
 
-    RefreshRateRanking ranking;
+    FrameRateRanking ranking;
     ranking.reserve(scores.size());
 
     std::transform(scores.begin(), scores.end(), back_inserter(ranking),
                    [](const RefreshRateScore& score) {
-                       return ScoredRefreshRate{score.modeIt->second, score.overallScore};
+                       return ScoredFrameRate{score.frameRateMode, score.overallScore};
                    });
 
     const bool noLayerScore = std::all_of(scores.begin(), scores.end(), [](RefreshRateScore score) {
@@ -569,7 +668,7 @@
         // range instead of picking a random score from the app range.
         if (noLayerScore) {
             ALOGV("Layers not scored");
-            return {rankRefreshRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
+            return {rankFrameRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
         } else {
             return {ranking, kNoSignals};
         }
@@ -580,7 +679,7 @@
     // vote we should not change it if we get a touch event. Only apply touch boost if it will
     // actually increase the refresh rate over the normal selection.
     const bool touchBoostForExplicitExact = [&] {
-        if (supportsFrameRateOverrideByContent()) {
+        if (supportsAppFrameRateOverrideByContent()) {
             // Enable touch boost if there are other layers besides exact
             return explicitExact + noVoteLayers != layers.size();
         } else {
@@ -589,12 +688,11 @@
         }
     }();
 
-    const auto touchRefreshRates = rankRefreshRates(anchorGroup, RefreshRateOrder::Descending);
-
+    const auto touchRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
     using fps_approx_ops::operator<;
 
     if (signals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
-        scores.front().modeIt->second->getFps() < touchRefreshRates.front().modePtr->getFps()) {
+        scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) {
         ALOGV("Touch Boost");
         return {touchRefreshRates, GlobalSignals{.touch = true}};
     }
@@ -603,7 +701,7 @@
     // current config
     if (noLayerScore && refreshRateOrder == RefreshRateOrder::Ascending) {
         const auto preferredDisplayMode = activeMode.getId();
-        return {rankRefreshRates(anchorGroup, RefreshRateOrder::Ascending, preferredDisplayMode),
+        return {rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, preferredDisplayMode),
                 kNoSignals};
     }
 
@@ -649,20 +747,13 @@
                                                 GlobalSignals globalSignals) const
         -> UidToFrameRateOverride {
     ATRACE_CALL();
-    ALOGV("%s: %zu layers", __func__, layers.size());
-
-    std::lock_guard lock(mLock);
-
-    // Prepare a set of supported display refresh rates for easy lookup
-    constexpr size_t kStaticCapacity = 8;
-    ftl::SmallMap<Fps, ftl::Unit, kStaticCapacity, FpsApproxEqual> supportedDisplayRefreshRates;
-    if (mConfig.enableFrameRateOverride ==
-        Config::FrameRateOverride::EnabledForNativeRefreshRates) {
-        for (const auto& [_, modePtr] : mDisplayModes) {
-            supportedDisplayRefreshRates.try_emplace(modePtr->getFps(), ftl::unit);
-        }
+    if (mConfig.enableFrameRateOverride == Config::FrameRateOverride::Disabled) {
+        return {};
     }
 
+    ALOGV("%s: %zu layers", __func__, layers.size());
+    std::lock_guard lock(mLock);
+
     const auto* policyPtr = getCurrentPolicyLocked();
     // We don't want to run lower than 30fps
     const Fps minFrameRate = std::max(policyPtr->appRequestRanges.render.min, 30_Hz, isApproxLess);
@@ -676,8 +767,8 @@
     for (unsigned n = numMultiples; n > 0; n--) {
         const Fps divisor = displayRefreshRate / n;
         if (mConfig.enableFrameRateOverride ==
-                    Config::FrameRateOverride::EnabledForNativeRefreshRates &&
-            !supportedDisplayRefreshRates.contains(divisor)) {
+                    Config::FrameRateOverride::AppOverrideNativeRefreshRates &&
+            !isNativeRefreshRate(divisor)) {
             continue;
         }
 
@@ -736,7 +827,7 @@
                                   [](const auto& lhsPair, const auto& rhsPair) {
                                       const float lhs = lhsPair.second;
                                       const float rhs = rhsPair.second;
-                                      return lhs < rhs && !ScoredRefreshRate::scoresEqual(lhs, rhs);
+                                      return lhs < rhs && !ScoredFrameRate::scoresEqual(lhs, rhs);
                                   });
         ALOGV("%s: overriding to %s for uid=%d", __func__, to_string(overrideFps).c_str(), uid);
         frameRateOverrides.emplace(uid, overrideFps);
@@ -765,10 +856,9 @@
 const DisplayModePtr& RefreshRateSelector::getMinRefreshRateByPolicyLocked() const {
     const auto& activeMode = *getActiveModeItLocked()->second;
 
-    for (const DisplayModeIterator modeIt : mPrimaryRefreshRates) {
-        const auto& mode = modeIt->second;
-        if (activeMode.getGroup() == mode->getGroup()) {
-            return mode;
+    for (const FrameRateMode& mode : mPrimaryFrameRates) {
+        if (activeMode.getGroup() == mode.modePtr->getGroup()) {
+            return mode.modePtr;
         }
     }
 
@@ -776,55 +866,72 @@
           to_string(activeMode).c_str());
 
     // Default to the lowest refresh rate.
-    return mPrimaryRefreshRates.front()->second;
+    return mPrimaryFrameRates.front().modePtr;
 }
 
 const DisplayModePtr& RefreshRateSelector::getMaxRefreshRateByPolicyLocked(int anchorGroup) const {
-    for (auto it = mPrimaryRefreshRates.rbegin(); it != mPrimaryRefreshRates.rend(); ++it) {
-        const auto& mode = (*it)->second;
-        if (anchorGroup == mode->getGroup()) {
-            return mode;
+    const DisplayModePtr* maxByAnchor = &mPrimaryFrameRates.back().modePtr;
+    const DisplayModePtr* max = &mPrimaryFrameRates.back().modePtr;
+
+    bool maxByAnchorFound = false;
+    for (auto it = mPrimaryFrameRates.rbegin(); it != mPrimaryFrameRates.rend(); ++it) {
+        using namespace fps_approx_ops;
+        if (it->modePtr->getFps() > (*max)->getFps()) {
+            max = &it->modePtr;
         }
+
+        if (anchorGroup == it->modePtr->getGroup() &&
+            it->modePtr->getFps() >= (*maxByAnchor)->getFps()) {
+            maxByAnchorFound = true;
+            maxByAnchor = &it->modePtr;
+        }
+    }
+
+    if (maxByAnchorFound) {
+        return *maxByAnchor;
     }
 
     ALOGE("Can't find max refresh rate by policy with the same group %d", anchorGroup);
 
     // Default to the highest refresh rate.
-    return mPrimaryRefreshRates.back()->second;
+    return *max;
 }
 
-auto RefreshRateSelector::rankRefreshRates(
-        std::optional<int> anchorGroupOpt, RefreshRateOrder refreshRateOrder,
-        std::optional<DisplayModeId> preferredDisplayModeOpt) const -> RefreshRateRanking {
-    std::deque<ScoredRefreshRate> ranking;
-
-    const auto rankRefreshRate = [&](DisplayModeIterator it) REQUIRES(mLock) {
-        const auto& mode = it->second;
-        if (anchorGroupOpt && mode->getGroup() != anchorGroupOpt) {
+auto RefreshRateSelector::rankFrameRates(std::optional<int> anchorGroupOpt,
+                                         RefreshRateOrder refreshRateOrder,
+                                         std::optional<DisplayModeId> preferredDisplayModeOpt) const
+        -> FrameRateRanking {
+    const char* const whence = __func__;
+    std::deque<ScoredFrameRate> ranking;
+    const auto rankFrameRate = [&](const FrameRateMode& frameRateMode) REQUIRES(mLock) {
+        const auto& modePtr = frameRateMode.modePtr;
+        if (anchorGroupOpt && modePtr->getGroup() != anchorGroupOpt) {
             return;
         }
 
-        float score = calculateRefreshRateScoreForFps(mode->getFps());
+        float score = calculateDistanceScoreFromMax(frameRateMode.fps);
         const bool inverseScore = (refreshRateOrder == RefreshRateOrder::Ascending);
         if (inverseScore) {
             score = 1.0f / score;
         }
         if (preferredDisplayModeOpt) {
-            if (*preferredDisplayModeOpt == mode->getId()) {
+            if (*preferredDisplayModeOpt == modePtr->getId()) {
                 constexpr float kScore = std::numeric_limits<float>::max();
-                ranking.push_front(ScoredRefreshRate{mode, kScore});
+                ranking.emplace_front(ScoredFrameRate{frameRateMode, kScore});
                 return;
             }
             constexpr float kNonPreferredModePenalty = 0.95f;
             score *= kNonPreferredModePenalty;
         }
-        ranking.push_back(ScoredRefreshRate{mode, score});
+        ALOGV("%s(%s) %s (%s) scored %.2f", whence, ftl::enum_string(refreshRateOrder).c_str(),
+              to_string(frameRateMode.fps).c_str(), to_string(modePtr->getFps()).c_str(), score);
+        ranking.emplace_back(ScoredFrameRate{frameRateMode, score});
     };
 
     if (refreshRateOrder == RefreshRateOrder::Ascending) {
-        std::for_each(mPrimaryRefreshRates.begin(), mPrimaryRefreshRates.end(), rankRefreshRate);
+        std::for_each(mPrimaryFrameRates.begin(), mPrimaryFrameRates.end(), rankFrameRate);
     } else {
-        std::for_each(mPrimaryRefreshRates.rbegin(), mPrimaryRefreshRates.rend(), rankRefreshRate);
+        std::for_each(mPrimaryFrameRates.rbegin(), mPrimaryFrameRates.rend(), rankFrameRate);
     }
 
     if (!ranking.empty() || !anchorGroupOpt) {
@@ -836,7 +943,7 @@
           refreshRateOrder == RefreshRateOrder::Ascending ? "min" : "max", anchorGroupOpt.value());
 
     constexpr std::optional<int> kNoAnchorGroup = std::nullopt;
-    return rankRefreshRates(kNoAnchorGroup, refreshRateOrder, preferredDisplayModeOpt);
+    return rankFrameRates(kNoAnchorGroup, refreshRateOrder, preferredDisplayModeOpt);
 }
 
 DisplayModePtr RefreshRateSelector::getActiveModePtr() const {
@@ -858,9 +965,9 @@
 void RefreshRateSelector::setActiveModeId(DisplayModeId modeId) {
     std::lock_guard lock(mLock);
 
-    // Invalidate the cached invocation to getRankedRefreshRates. This forces
-    // the refresh rate to be recomputed on the next call to getRankedRefreshRates.
-    mGetRankedRefreshRatesCache.reset();
+    // Invalidate the cached invocation to getRankedFrameRates. This forces
+    // the refresh rate to be recomputed on the next call to getRankedFrameRates.
+    mGetRankedFrameRatesCache.reset();
 
     mActiveModeIt = mDisplayModes.find(modeId);
     LOG_ALWAYS_FATAL_IF(mActiveModeIt == mDisplayModes.end());
@@ -895,16 +1002,15 @@
 void RefreshRateSelector::updateDisplayModes(DisplayModes modes, DisplayModeId activeModeId) {
     std::lock_guard lock(mLock);
 
-    // Invalidate the cached invocation to getRankedRefreshRates. This forces
-    // the refresh rate to be recomputed on the next call to getRankedRefreshRates.
-    mGetRankedRefreshRatesCache.reset();
+    // Invalidate the cached invocation to getRankedFrameRates. This forces
+    // the refresh rate to be recomputed on the next call to getRankedFrameRates.
+    mGetRankedFrameRatesCache.reset();
 
     mDisplayModes = std::move(modes);
     mActiveModeIt = mDisplayModes.find(activeModeId);
     LOG_ALWAYS_FATAL_IF(mActiveModeIt == mDisplayModes.end());
 
-    const auto sortedModes =
-            sortByRefreshRate(mDisplayModes, [](const DisplayMode&) { return true; });
+    const auto sortedModes = sortByRefreshRate(mDisplayModes);
     mMinRefreshRateModeIt = sortedModes.front();
     mMaxRefreshRateModeIt = sortedModes.back();
 
@@ -915,15 +1021,23 @@
     mFrameRateOverrideConfig = [&] {
         switch (mConfig.enableFrameRateOverride) {
             case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverride:
             case Config::FrameRateOverride::Enabled:
                 return mConfig.enableFrameRateOverride;
-            case Config::FrameRateOverride::EnabledForNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
                 return shouldEnableFrameRateOverride(sortedModes)
-                        ? Config::FrameRateOverride::EnabledForNativeRefreshRates
+                        ? Config::FrameRateOverride::AppOverrideNativeRefreshRates
                         : Config::FrameRateOverride::Disabled;
         }
     }();
 
+    if (mConfig.enableFrameRateOverride ==
+        Config::FrameRateOverride::AppOverrideNativeRefreshRates) {
+        for (const auto& [_, mode] : mDisplayModes) {
+            mAppOverrideNativeRefreshRates.try_emplace(mode->getFps(), ftl::unit);
+        }
+    }
+
     constructAvailableRefreshRates();
 }
 
@@ -939,9 +1053,13 @@
         return false;
     }
 
-    using namespace fps_approx_ops;
-    return policy.appRequestRanges.physical.min <= policy.primaryRanges.physical.min &&
-            policy.appRequestRanges.physical.max >= policy.primaryRanges.physical.max;
+    const auto& primaryRanges = policy.primaryRanges;
+    const auto& appRequestRanges = policy.appRequestRanges;
+    ALOGE_IF(!appRequestRanges.physical.includes(primaryRanges.physical),
+             "Physical range is invalid");
+    ALOGE_IF(!appRequestRanges.render.includes(primaryRanges.render), "Render range is invalid");
+
+    return primaryRanges.valid() && appRequestRanges.valid();
 }
 
 auto RefreshRateSelector::setPolicy(const PolicyVariant& policy) -> SetPolicyResult {
@@ -979,7 +1097,7 @@
             return SetPolicyResult::Invalid;
         }
 
-        mGetRankedRefreshRatesCache.reset();
+        mGetRankedFrameRatesCache.reset();
 
         if (*getCurrentPolicyLocked() == oldPolicy) {
             return SetPolicyResult::Unchanged;
@@ -1016,9 +1134,9 @@
 
 bool RefreshRateSelector::isModeAllowed(DisplayModeId modeId) const {
     std::lock_guard lock(mLock);
-    return std::any_of(mAppRequestRefreshRates.begin(), mAppRequestRefreshRates.end(),
-                       [modeId](DisplayModeIterator modeIt) {
-                           return modeIt->second->getId() == modeId;
+    return std::any_of(mAppRequestFrameRates.begin(), mAppRequestFrameRates.end(),
+                       [modeId](const FrameRateMode& frameRateMode) {
+                           return frameRateMode.modePtr->getId() == modeId;
                        });
 }
 
@@ -1029,33 +1147,35 @@
 
     const auto& defaultMode = mDisplayModes.get(policy->defaultMode)->get();
 
-    const auto filterRefreshRates = [&](FpsRange range, const char* rangeName) REQUIRES(mLock) {
-        const auto filter = [&](const DisplayMode& mode) {
+    const auto filterRefreshRates = [&](const FpsRanges& ranges,
+                                        const char* rangeName) REQUIRES(mLock) {
+        const auto filterModes = [&](const DisplayMode& mode) {
             return mode.getResolution() == defaultMode->getResolution() &&
                     mode.getDpi() == defaultMode->getDpi() &&
                     (policy->allowGroupSwitching || mode.getGroup() == defaultMode->getGroup()) &&
-                    range.includes(mode.getFps());
+                    ranges.physical.includes(mode.getFps()) &&
+                    (supportsFrameRateOverride() || ranges.render.includes(mode.getFps()));
         };
 
-        const auto modes = sortByRefreshRate(mDisplayModes, filter);
-        LOG_ALWAYS_FATAL_IF(modes.empty(), "No matching modes for %s range %s", rangeName,
-                            to_string(range).c_str());
+        const auto frameRateModes = createFrameRateModes(filterModes, ranges.render);
+        LOG_ALWAYS_FATAL_IF(frameRateModes.empty(),
+                            "No matching frame rate modes for %s physicalRange %s", rangeName,
+                            to_string(ranges.physical).c_str());
 
         const auto stringifyModes = [&] {
             std::string str;
-            for (const auto modeIt : modes) {
-                str += to_string(modeIt->second->getFps());
-                str.push_back(' ');
+            for (const auto& frameRateMode : frameRateModes) {
+                str += to_string(frameRateMode) + " ";
             }
             return str;
         };
-        ALOGV("%s refresh rates: %s", rangeName, stringifyModes().c_str());
+        ALOGV("%s render rates: %s", rangeName, stringifyModes().c_str());
 
-        return modes;
+        return frameRateModes;
     };
 
-    mPrimaryRefreshRates = filterRefreshRates(policy->primaryRanges.physical, "primary");
-    mAppRequestRefreshRates = filterRefreshRates(policy->appRequestRanges.physical, "app request");
+    mPrimaryFrameRates = filterRefreshRates(policy->primaryRanges, "primary");
+    mAppRequestFrameRates = filterRefreshRates(policy->appRequestRanges, "app request");
 }
 
 Fps RefreshRateSelector::findClosestKnownFrameRate(Fps frameRate) const {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index 65ee487..89ebeea 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -24,9 +24,11 @@
 
 #include <ftl/concat.h>
 #include <ftl/optional.h>
+#include <ftl/unit.h>
 #include <gui/DisplayEventReceiver.h>
 
 #include <scheduler/Fps.h>
+#include <scheduler/FrameRateMode.h>
 #include <scheduler/Seamlessness.h>
 
 #include "DisplayHardware/DisplayMode.h"
@@ -58,6 +60,9 @@
     static constexpr nsecs_t MARGIN_FOR_PERIOD_CALCULATION =
             std::chrono::nanoseconds(800us).count();
 
+    // The lowest Render Frame Rate that will ever be selected
+    static constexpr Fps kMinSupportedFrameRate = 20_Hz;
+
     class Policy {
         static constexpr int kAllowGroupSwitchingDefault = false;
 
@@ -196,12 +201,12 @@
         }
     };
 
-    struct ScoredRefreshRate {
-        DisplayModePtr modePtr;
+    struct ScoredFrameRate {
+        FrameRateMode frameRateMode;
         float score = 0.0f;
 
-        bool operator==(const ScoredRefreshRate& other) const {
-            return modePtr == other.modePtr && score == other.score;
+        bool operator==(const ScoredFrameRate& other) const {
+            return frameRateMode == other.frameRateMode && score == other.score;
         }
 
         static bool scoresEqual(float lhs, float rhs) {
@@ -210,25 +215,25 @@
         }
 
         struct DescendingScore {
-            bool operator()(const ScoredRefreshRate& lhs, const ScoredRefreshRate& rhs) const {
+            bool operator()(const ScoredFrameRate& lhs, const ScoredFrameRate& rhs) const {
                 return lhs.score > rhs.score && !scoresEqual(lhs.score, rhs.score);
             }
         };
     };
 
-    using RefreshRateRanking = std::vector<ScoredRefreshRate>;
+    using FrameRateRanking = std::vector<ScoredFrameRate>;
 
-    struct RankedRefreshRates {
-        RefreshRateRanking ranking; // Ordered by descending score.
+    struct RankedFrameRates {
+        FrameRateRanking ranking; // Ordered by descending score.
         GlobalSignals consideredSignals;
 
-        bool operator==(const RankedRefreshRates& other) const {
+        bool operator==(const RankedFrameRates& other) const {
             return ranking == other.ranking && consideredSignals == other.consideredSignals;
         }
     };
 
-    RankedRefreshRates getRankedRefreshRates(const std::vector<LayerRequirement>&,
-                                             GlobalSignals) const EXCLUDES(mLock);
+    RankedFrameRates getRankedFrameRates(const std::vector<LayerRequirement>&, GlobalSignals) const
+            EXCLUDES(mLock);
 
     FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
         std::lock_guard lock(mLock);
@@ -257,9 +262,12 @@
 
             // Override the frame rate for an app to a value which is also
             // a display refresh rate
-            EnabledForNativeRefreshRates,
+            AppOverrideNativeRefreshRates,
 
             // Override the frame rate for an app to any value
+            AppOverride,
+
+            // Override the frame rate for all apps and all values.
             Enabled,
 
             ftl_last = Enabled
@@ -291,10 +299,13 @@
 
     // Returns whether switching modes (refresh rate or resolution) is possible.
     // TODO(b/158780872): Consider HAL support, and skip frame rate detection if the modes only
-    // differ in resolution.
+    //  differ in resolution. Once Config::FrameRateOverride::Enabled becomes the default,
+    //  we can probably remove canSwitch altogether since all devices will be able
+    //  to switch to a frame rate divisor.
     bool canSwitch() const EXCLUDES(mLock) {
         std::lock_guard lock(mLock);
-        return mDisplayModes.size() > 1;
+        return mDisplayModes.size() > 1 ||
+                mFrameRateOverrideConfig == Config::FrameRateOverride::Enabled;
     }
 
     // Class to enumerate options around toggling the kernel timer on and off.
@@ -307,10 +318,14 @@
     // refresh rates.
     KernelIdleTimerAction getIdleTimerAction() const;
 
-    bool supportsFrameRateOverrideByContent() const {
+    bool supportsAppFrameRateOverrideByContent() const {
         return mFrameRateOverrideConfig != Config::FrameRateOverride::Disabled;
     }
 
+    bool supportsFrameRateOverride() const {
+        return mFrameRateOverrideConfig == Config::FrameRateOverride::Enabled;
+    }
+
     // Return the display refresh rate divisor to match the layer
     // frame rate, or 0 if the display refresh rate is not a multiple of the
     // layer refresh rate.
@@ -387,8 +402,8 @@
     // See mActiveModeIt for thread safety.
     DisplayModeIterator getActiveModeItLocked() const REQUIRES(mLock);
 
-    RankedRefreshRates getRankedRefreshRatesLocked(const std::vector<LayerRequirement>&,
-                                                   GlobalSignals) const REQUIRES(mLock);
+    RankedFrameRates getRankedFrameRatesLocked(const std::vector<LayerRequirement>& layers,
+                                               GlobalSignals signals) const REQUIRES(mLock);
 
     // Returns number of display frames and remainder when dividing the layer refresh period by
     // display refresh period.
@@ -404,18 +419,24 @@
 
     struct RefreshRateScoreComparator;
 
-    enum class RefreshRateOrder { Ascending, Descending };
+    enum class RefreshRateOrder {
+        Ascending,
+        Descending,
+
+        ftl_last = Descending
+    };
 
     // Only uses the primary range, not the app request range.
-    RefreshRateRanking rankRefreshRates(std::optional<int> anchorGroupOpt, RefreshRateOrder,
-                                        std::optional<DisplayModeId> preferredDisplayModeOpt =
-                                                std::nullopt) const REQUIRES(mLock);
+    FrameRateRanking rankFrameRates(
+            std::optional<int> anchorGroupOpt, RefreshRateOrder refreshRateOrder,
+            std::optional<DisplayModeId> preferredDisplayModeOpt = std::nullopt) const
+            REQUIRES(mLock);
 
     const Policy* getCurrentPolicyLocked() const REQUIRES(mLock);
     bool isPolicyValidLocked(const Policy& policy) const REQUIRES(mLock);
 
     // Returns the refresh rate score as a ratio to max refresh rate, which has a score of 1.
-    float calculateRefreshRateScoreForFps(Fps refreshRate) const REQUIRES(mLock);
+    float calculateDistanceScoreFromMax(Fps refreshRate) const REQUIRES(mLock);
     // calculates a score for a layer. Used to determine the display refresh rate
     // and the frame rate override for certains applications.
     float calculateLayerScoreLocked(const LayerRequirement&, Fps refreshRate,
@@ -436,11 +457,27 @@
                                                              : mIdleTimerCallbacks->platform;
     }
 
+    bool isNativeRefreshRate(Fps fps) const REQUIRES(mLock) {
+        LOG_ALWAYS_FATAL_IF(mConfig.enableFrameRateOverride !=
+                                    Config::FrameRateOverride::AppOverrideNativeRefreshRates,
+                            "should only be called when "
+                            "Config::FrameRateOverride::AppOverrideNativeRefreshRates is used");
+        return mAppOverrideNativeRefreshRates.contains(fps);
+    }
+
+    std::vector<FrameRateMode> createFrameRateModes(
+            std::function<bool(const DisplayMode&)>&& filterModes, const FpsRange&) const
+            REQUIRES(mLock);
+
     // The display modes of the active display. The DisplayModeIterators below are pointers into
     // this container, so must be invalidated whenever the DisplayModes change. The Policy below
     // is also dependent, so must be reset as well.
     DisplayModes mDisplayModes GUARDED_BY(mLock);
 
+    // Set of supported display refresh rates for easy lookup
+    // when FrameRateOverride::AppOverrideNativeRefreshRates is in use.
+    ftl::SmallMap<Fps, ftl::Unit, 8, FpsApproxEqual> mAppOverrideNativeRefreshRates;
+
     // Written under mLock exclusively from kMainThreadContext, so reads from kMainThreadContext
     // need not be under mLock.
     DisplayModeIterator mActiveModeIt GUARDED_BY(mLock) GUARDED_BY(kMainThreadContext);
@@ -449,8 +486,8 @@
     DisplayModeIterator mMaxRefreshRateModeIt GUARDED_BY(mLock);
 
     // Display modes that satisfy the Policy's ranges, filtered and sorted by refresh rate.
-    std::vector<DisplayModeIterator> mPrimaryRefreshRates GUARDED_BY(mLock);
-    std::vector<DisplayModeIterator> mAppRequestRefreshRates GUARDED_BY(mLock);
+    std::vector<FrameRateMode> mPrimaryFrameRates GUARDED_BY(mLock);
+    std::vector<FrameRateMode> mAppRequestFrameRates GUARDED_BY(mLock);
 
     Policy mDisplayManagerPolicy GUARDED_BY(mLock);
     std::optional<Policy> mOverridePolicy GUARDED_BY(mLock);
@@ -466,11 +503,11 @@
     const Config mConfig;
     Config::FrameRateOverride mFrameRateOverrideConfig;
 
-    struct GetRankedRefreshRatesCache {
+    struct GetRankedFrameRatesCache {
         std::pair<std::vector<LayerRequirement>, GlobalSignals> arguments;
-        RankedRefreshRates result;
+        RankedFrameRates result;
     };
-    mutable std::optional<GetRankedRefreshRatesCache> mGetRankedRefreshRatesCache GUARDED_BY(mLock);
+    mutable std::optional<GetRankedFrameRatesCache> mGetRankedFrameRatesCache GUARDED_BY(mLock);
 
     // Declare mIdleTimer last to ensure its thread joins before the mutex/callbacks are destroyed.
     std::mutex mIdleTimerCallbacksMutex;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index f1fcc88..0c541f9 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -152,7 +152,7 @@
 
 std::optional<Fps> Scheduler::getFrameRateOverride(uid_t uid) const {
     const bool supportsFrameRateOverrideByContent =
-            leaderSelectorPtr()->supportsFrameRateOverrideByContent();
+            leaderSelectorPtr()->supportsAppFrameRateOverrideByContent();
     return mFrameRateOverrideMappings
             .getFrameRateOverrideForUid(uid, supportsFrameRateOverrideByContent);
 }
@@ -268,7 +268,7 @@
 
 void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDisplayId displayId) {
     const bool supportsFrameRateOverrideByContent =
-            leaderSelectorPtr()->supportsFrameRateOverrideByContent();
+            leaderSelectorPtr()->supportsAppFrameRateOverrideByContent();
 
     std::vector<FrameRateOverride> overrides =
             mFrameRateOverrideMappings.getAllFrameRateOverrides(supportsFrameRateOverrideByContent);
@@ -707,7 +707,7 @@
 auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap {
     ATRACE_CALL();
 
-    using RankedRefreshRates = RefreshRateSelector::RankedRefreshRates;
+    using RankedRefreshRates = RefreshRateSelector::RankedFrameRates;
     display::PhysicalDisplayVector<RankedRefreshRates> perDisplayRanking;
 
     // Tallies the score of a refresh rate across `displayCount` displays.
@@ -726,9 +726,10 @@
 
     for (const auto& [id, selectorPtr] : mRefreshRateSelectors) {
         auto rankedRefreshRates =
-                selectorPtr->getRankedRefreshRates(mPolicy.contentRequirements, globalSignals);
+                selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, globalSignals);
 
-        for (const auto& [modePtr, score] : rankedRefreshRates.ranking) {
+        for (const auto& [frameRateMode, score] : rankedRefreshRates.ranking) {
+            const auto& modePtr = frameRateMode.modePtr;
             const auto [it, inserted] = refreshRateTallies.try_emplace(modePtr->getFps(), score);
 
             if (!inserted) {
@@ -771,16 +772,18 @@
 
     for (auto& [ranking, signals] : perDisplayRanking) {
         if (!chosenFps) {
-            auto& [modePtr, _] = ranking.front();
+            const auto& [frameRateMode, _] = ranking.front();
+            const auto& modePtr = frameRateMode.modePtr;
             modeChoices.try_emplace(modePtr->getPhysicalDisplayId(),
-                                    DisplayModeChoice{std::move(modePtr), signals});
+                                    DisplayModeChoice{modePtr, signals});
             continue;
         }
 
-        for (auto& [modePtr, _] : ranking) {
+        for (auto& [frameRateMode, _] : ranking) {
+            const auto& modePtr = frameRateMode.modePtr;
             if (modePtr->getFps() == *chosenFps) {
                 modeChoices.try_emplace(modePtr->getPhysicalDisplayId(),
-                                        DisplayModeChoice{std::move(modePtr), signals});
+                                        DisplayModeChoice{modePtr, signals});
                 break;
             }
         }
@@ -804,10 +807,10 @@
     if (mPolicy.mode) {
         const auto ranking =
                 leaderSelectorPtr()
-                        ->getRankedRefreshRates(mPolicy.contentRequirements, makeGlobalSignals())
+                        ->getRankedFrameRates(mPolicy.contentRequirements, makeGlobalSignals())
                         .ranking;
 
-        mPolicy.mode = ranking.front().modePtr;
+        mPolicy.mode = ranking.front().frameRateMode.modePtr;
     }
     return mPolicy.mode;
 }
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
index 31b1d69..5522ff8 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
@@ -66,6 +66,7 @@
     Fps max = Fps::fromValue(std::numeric_limits<float>::max());
 
     bool includes(Fps) const;
+    bool includes(FpsRange) const;
 };
 
 struct FpsRanges {
@@ -75,6 +76,8 @@
     // the range of frame rates that refers to the render rate, which is
     // the rate that frames are swapped.
     FpsRange render;
+
+    bool valid() const;
 };
 
 static_assert(std::is_trivially_copyable_v<Fps>);
@@ -159,6 +162,16 @@
     return min <= fps && fps <= max;
 }
 
+inline bool FpsRange::includes(FpsRange range) const {
+    using namespace fps_approx_ops;
+    return min <= range.min && max >= range.max;
+}
+
+inline bool FpsRanges::valid() const {
+    using fps_approx_ops::operator>=;
+    return physical.max >= render.max;
+}
+
 struct FpsApproxEqual {
     bool operator()(Fps lhs, Fps rhs) const { return isApproxEqual(lhs, rhs); }
 };
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
new file mode 100644
index 0000000..670ab45
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <scheduler/Fps.h>
+
+// TODO(b/241285191): Pull this to <ui/DisplayMode.h>
+#include "DisplayHardware/DisplayMode.h"
+
+namespace android::scheduler {
+
+struct FrameRateMode {
+    Fps fps; // The render frame rate, which is a divisor of modePtr->getFps().
+    DisplayModePtr modePtr;
+
+    bool operator==(const FrameRateMode& other) const {
+        return isApproxEqual(fps, other.fps) && modePtr == other.modePtr;
+    }
+
+    bool operator!=(const FrameRateMode& other) const { return !(*this == other); }
+};
+
+inline std::string to_string(const FrameRateMode& mode) {
+    if (mode.modePtr) {
+        return to_string(mode.fps) + " (" + to_string(mode.modePtr->getFps()) + ")";
+    }
+    return "{invalid}";
+}
+
+} // namespace android::scheduler
\ No newline at end of file
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 5df09d6..6961f7c 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -180,6 +180,7 @@
 using base::StringAppendF;
 using display::PhysicalDisplay;
 using display::PhysicalDisplays;
+using frontend::TransactionHandler;
 using gui::DisplayInfo;
 using gui::GameMode;
 using gui::IDisplayEventConnection;
@@ -1391,7 +1392,26 @@
     return NO_ERROR;
 }
 
-status_t SurfaceFlinger::getOverlaySupport(gui::OverlayProperties* /*outProperties*/) const {
+status_t SurfaceFlinger::getOverlaySupport(gui::OverlayProperties* outProperties) const {
+    const auto& aidlProperties = getHwComposer().getOverlaySupport();
+    // convert aidl OverlayProperties to gui::OverlayProperties
+    outProperties->combinations.reserve(aidlProperties.combinations.size());
+    for (const auto& combination : aidlProperties.combinations) {
+        std::vector<int32_t> pixelFormats;
+        pixelFormats.reserve(combination.pixelFormats.size());
+        std::transform(combination.pixelFormats.cbegin(), combination.pixelFormats.cend(),
+                       std::back_inserter(pixelFormats),
+                       [](const auto& val) { return static_cast<int32_t>(val); });
+        std::vector<int32_t> dataspaces;
+        dataspaces.reserve(combination.dataspaces.size());
+        std::transform(combination.dataspaces.cbegin(), combination.dataspaces.cend(),
+                       std::back_inserter(dataspaces),
+                       [](const auto& val) { return static_cast<int32_t>(val); });
+        gui::OverlayProperties::SupportedBufferCombinations outCombination;
+        outCombination.pixelFormats = std::move(pixelFormats);
+        outCombination.dataspaces = std::move(dataspaces);
+        outProperties->combinations.emplace_back(outCombination);
+    }
     return NO_ERROR;
 }
 
@@ -2790,7 +2810,11 @@
             }
 
             if (sysprop::frame_rate_override_for_native_rates(true)) {
-                return Config::FrameRateOverride::EnabledForNativeRefreshRates;
+                return Config::FrameRateOverride::AppOverrideNativeRefreshRates;
+            }
+
+            if (!base::GetBoolProperty("debug.sf.frame_rate_override_global"s, false)) {
+                return Config::FrameRateOverride::AppOverride;
             }
 
             return Config::FrameRateOverride::Enabled;
@@ -3302,7 +3326,7 @@
         if (!layer->needsInputInfo()) return;
 
         const auto opt = mFrontEndDisplayInfos.get(layer->getLayerStack())
-                                 .transform([](const FrontEndDisplayInfo& info) {
+                                 .transform([](const frontend::DisplayInfo& info) {
                                      return Layer::InputDisplayArgs{&info.transform, info.isSecure};
                                  });
 
@@ -6374,8 +6398,10 @@
                 std::unique_ptr<RenderArea> renderArea = renderAreaFuture.get();
                 if (!renderArea) {
                     ALOGW("Skipping screen capture because of invalid render area.");
-                    captureResults.fenceResult = base::unexpected(NO_MEMORY);
-                    captureListener->onScreenCaptureCompleted(captureResults);
+                    if (captureListener) {
+                        captureResults.fenceResult = base::unexpected(NO_MEMORY);
+                        captureListener->onScreenCaptureCompleted(captureResults);
+                    }
                     return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share();
                 }
 
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 8776904..751d1e5 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -66,7 +66,7 @@
 #include "DisplayIdGenerator.h"
 #include "Effects/Daltonizer.h"
 #include "FlagManager.h"
-#include "FrontEnd/FrontEndDisplayInfo.h"
+#include "FrontEnd/DisplayInfo.h"
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/TransactionHandler.h"
 #include "LayerVector.h"
@@ -121,6 +121,7 @@
 class ScreenCapturer;
 class WindowInfosListenerInvoker;
 
+using frontend::TransactionHandler;
 using gui::CaptureArgs;
 using gui::DisplayCaptureArgs;
 using gui::IRegionSamplingListener;
@@ -1372,7 +1373,7 @@
     } mPowerHintSessionMode;
 
     TransactionHandler mTransactionHandler;
-    display::DisplayMap<ui::LayerStack, FrontEndDisplayInfo> mFrontEndDisplayInfos;
+    display::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos;
 };
 
 class SurfaceComposerAIDL : public gui::BnSurfaceComposer {
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index e5a9dd4..e860d88 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -68,6 +68,8 @@
             return SurfaceflingerStatsLayerInfo::GAME_MODE_PERFORMANCE;
         case GameMode::Battery:
             return SurfaceflingerStatsLayerInfo::GAME_MODE_BATTERY;
+        case GameMode::Custom:
+            return SurfaceflingerStatsLayerInfo::GAME_MODE_CUSTOM;
         default:
             return SurfaceflingerStatsLayerInfo::GAME_MODE_UNSPECIFIED;
     }
diff --git a/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto b/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto
index e45757d..d4d444e 100644
--- a/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto
+++ b/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto
@@ -173,6 +173,7 @@
          GAME_MODE_STANDARD = 2;
          GAME_MODE_PERFORMANCE = 3;
          GAME_MODE_BATTERY = 4;
+         GAME_MODE_CUSTOM = 5;
     }
 
     // Game mode that the layer was running at. Used to track user engagement
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index bd11a37..950e6d3 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -353,7 +353,7 @@
     const RefreshRateSelector::GlobalSignals globalSignals = {.touch = false, .idle = false};
     std::vector<LayerRequirement> layers = {{.weight = mFdp.ConsumeFloatingPoint<float>()}};
 
-    refreshRateSelector.getRankedRefreshRates(layers, globalSignals);
+    refreshRateSelector.getRankedFrameRates(layers, globalSignals);
 
     layers[0].name = mFdp.ConsumeRandomLengthString(kRandomStringLength);
     layers[0].ownerUid = mFdp.ConsumeIntegral<uint16_t>();
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index d88da4d..0114577 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -85,6 +85,7 @@
         "FpsTest.cpp",
         "FramebufferSurfaceTest.cpp",
         "FrameRateOverrideMappingsTest.cpp",
+        "FrameRateSelectionPriorityTest.cpp",
         "FrameTimelineTest.cpp",
         "GameModeTest.cpp",
         "HWComposerTest.cpp",
@@ -92,6 +93,7 @@
         "LayerHistoryTest.cpp",
         "LayerInfoTest.cpp",
         "LayerMetadataTest.cpp",
+        "LayerLifecycleManagerTest.cpp",
         "LayerTest.cpp",
         "LayerTestUtils.cpp",
         "MessageQueueTest.cpp",
@@ -112,7 +114,6 @@
         "SchedulerTest.cpp",
         "SetFrameRateTest.cpp",
         "RefreshRateSelectorTest.cpp",
-        "RefreshRateSelectionTest.cpp",
         "RefreshRateStatsTest.cpp",
         "RegionSamplingTest.cpp",
         "TimeStatsTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp
similarity index 100%
rename from services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp
rename to services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index 342c646..8f89a8c 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -146,6 +146,8 @@
                                     {kMetadata2Name, kMetadata2Mandatory},
                             }),
                             Return(hardware::graphics::composer::V2_4::Error::NONE)));
+    EXPECT_CALL(*mHal, getOverlaySupport(_)).WillOnce(Return(HalError::NONE));
+
     EXPECT_CALL(*mHal, registerCallback(_));
 
     mHwc.setCallback(mCallback);
@@ -162,6 +164,7 @@
     EXPECT_CALL(*mHal, getCapabilities()).WillOnce(Return(std::vector<aidl::Capability>{}));
     EXPECT_CALL(*mHal, getLayerGenericMetadataKeys(_))
             .WillOnce(Return(hardware::graphics::composer::V2_4::Error::UNSUPPORTED));
+    EXPECT_CALL(*mHal, getOverlaySupport(_)).WillOnce(Return(HalError::UNSUPPORTED));
     EXPECT_CALL(*mHal, registerCallback(_));
 
     mHwc.setCallback(mCallback);
diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
new file mode 100644
index 0000000..89440a6
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
@@ -0,0 +1,453 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "FrontEnd/LayerHandle.h"
+#include "FrontEnd/LayerLifecycleManager.h"
+#include "Layer.h"
+#include "gui/SurfaceComposerClient.h"
+
+using namespace android::surfaceflinger;
+
+namespace android::surfaceflinger::frontend {
+
+namespace {
+LayerCreationArgs createArgs(uint32_t id, bool canBeRoot, wp<IBinder> parent, wp<IBinder> mirror) {
+    LayerCreationArgs args(nullptr, nullptr, "testlayer", 0, {}, std::make_optional(id));
+    args.addToRoot = canBeRoot;
+    args.parentHandle = parent;
+    args.mirrorLayerHandle = mirror;
+    return args;
+}
+} // namespace
+
+// To run test:
+/**
+ mp :libsurfaceflinger_unittest && adb sync; adb shell \
+    /data/nativetest/libsurfaceflinger_unittest/libsurfaceflinger_unittest \
+    --gtest_filter="LayerLifecycleManagerTest.*" --gtest_repeat=100 \
+    --gtest_shuffle \
+    --gtest_brief=1
+*/
+class ExpectLayerLifecycleListener : public LayerLifecycleManager::ILifecycleListener {
+public:
+    void onLayerAdded(const RequestedLayerState& layer) override {
+        mActualLayersAdded.emplace(layer.id);
+    };
+    void onLayerDestroyed(const RequestedLayerState& layer) override {
+        mActualLayersDestroyed.emplace(layer.id);
+    };
+
+    void expectLayersAdded(const std::unordered_set<uint32_t>& expectedLayersAdded) {
+        EXPECT_EQ(expectedLayersAdded, mActualLayersAdded);
+        mActualLayersAdded.clear();
+    }
+    void expectLayersDestroyed(const std::unordered_set<uint32_t>& expectedLayersDestroyed) {
+        EXPECT_EQ(expectedLayersDestroyed, mActualLayersDestroyed);
+        mActualLayersDestroyed.clear();
+    }
+
+    std::unordered_set<uint32_t> mActualLayersAdded;
+    std::unordered_set<uint32_t> mActualLayersDestroyed;
+};
+
+class LayerLifecycleManagerTest : public testing::Test {
+protected:
+    std::unique_ptr<RequestedLayerState> rootLayer(uint32_t id) {
+        return std::make_unique<RequestedLayerState>(
+                createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/nullptr, /*mirror=*/nullptr));
+    }
+
+    std::unique_ptr<RequestedLayerState> childLayer(uint32_t id, uint32_t parentId) {
+        mHandles[parentId] = sp<LayerHandle>::make(parentId);
+        return std::make_unique<RequestedLayerState>(createArgs(/*id=*/id, /*canBeRoot=*/false,
+                                                                /*parent=*/mHandles[parentId],
+                                                                /*mirror=*/nullptr));
+    }
+
+    TransactionState reparentLayer(uint32_t id, uint32_t newParentId) {
+        TransactionState transaction;
+        transaction.states.push_back({});
+
+        if (newParentId == UNASSIGNED_LAYER_ID) {
+            transaction.states.front().state.parentSurfaceControlForChild = nullptr;
+        } else {
+            transaction.states.front().state.parentSurfaceControlForChild =
+                    sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(),
+                                             sp<LayerHandle>::make(newParentId),
+                                             static_cast<int32_t>(newParentId), "Test");
+        }
+        transaction.states.front().state.what = layer_state_t::eReparent;
+        transaction.states.front().state.surface = sp<LayerHandle>::make(id);
+        return transaction;
+    }
+
+    TransactionState setLayer(uint32_t id, int32_t z) {
+        TransactionState transaction;
+        transaction.states.push_back({});
+        transaction.states.front().state.z = z;
+        transaction.states.front().state.what = layer_state_t::eLayerChanged;
+        transaction.states.front().state.surface = sp<LayerHandle>::make(id);
+        return transaction;
+    }
+
+    TransactionState makeRelative(uint32_t id, uint32_t relativeParentId) {
+        TransactionState transaction;
+        transaction.states.push_back({});
+
+        if (relativeParentId == UNASSIGNED_LAYER_ID) {
+            transaction.states.front().state.relativeLayerSurfaceControl = nullptr;
+        } else {
+            transaction.states.front().state.relativeLayerSurfaceControl =
+                    sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(),
+                                             sp<LayerHandle>::make(relativeParentId),
+                                             static_cast<int32_t>(relativeParentId), "Test");
+        }
+        transaction.states.front().state.what = layer_state_t::eRelativeLayerChanged;
+        transaction.states.front().state.surface = sp<LayerHandle>::make(id);
+        return transaction;
+    }
+
+    RequestedLayerState* getRequestedLayerState(LayerLifecycleManager& lifecycleManager,
+                                                uint32_t layerId) {
+        return lifecycleManager.getLayerFromId(layerId);
+    }
+
+    std::unordered_map<uint32_t, sp<LayerHandle>> mHandles;
+};
+
+TEST_F(LayerLifecycleManagerTest, addLayers) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    layers.emplace_back(rootLayer(2));
+    layers.emplace_back(rootLayer(3));
+    lifecycleManager.addLayers(std::move(layers));
+    lifecycleManager.onHandlesDestroyed({1, 2, 3});
+    EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+    lifecycleManager.commitChanges();
+    EXPECT_FALSE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+    listener->expectLayersAdded({1, 2, 3});
+    listener->expectLayersDestroyed({1, 2, 3});
+}
+
+TEST_F(LayerLifecycleManagerTest, updateLayerStates) {
+    LayerLifecycleManager lifecycleManager;
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    lifecycleManager.addLayers(std::move(layers));
+
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.z = 2;
+    transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
+    sp<LayerHandle> handle = sp<LayerHandle>::make(1u);
+    transactions.back().states.front().state.surface = handle;
+    lifecycleManager.applyTransactions(transactions);
+    transactions.clear();
+
+    auto& managedLayers = lifecycleManager.getLayers();
+    ASSERT_EQ(managedLayers.size(), 1u);
+
+    EXPECT_EQ(managedLayers.front()->z, 2);
+    EXPECT_TRUE(managedLayers.front()->changes.test(RequestedLayerState::Changes::Z));
+
+    EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+    lifecycleManager.commitChanges();
+    EXPECT_FALSE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+    ASSERT_EQ(managedLayers.size(), 1u);
+    EXPECT_FALSE(managedLayers.front()->changes.test(RequestedLayerState::Changes::Z));
+
+    // apply transactions that do not affect the hierarchy
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.backgroundBlurRadius = 22;
+    transactions.back().states.front().state.what = layer_state_t::eBackgroundBlurRadiusChanged;
+    transactions.back().states.front().state.surface = handle;
+    lifecycleManager.applyTransactions(transactions);
+    EXPECT_FALSE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+    lifecycleManager.commitChanges();
+    EXPECT_FALSE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+    EXPECT_EQ(managedLayers.front()->backgroundBlurRadius, 22u);
+}
+
+TEST_F(LayerLifecycleManagerTest, layerWithoutHandleIsDestroyed) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    layers.emplace_back(rootLayer(2));
+    lifecycleManager.addLayers(std::move(layers));
+    lifecycleManager.onHandlesDestroyed({1});
+    lifecycleManager.commitChanges();
+
+    SCOPED_TRACE("layerWithoutHandleIsDestroyed");
+    listener->expectLayersAdded({1, 2});
+    listener->expectLayersDestroyed({1});
+}
+
+TEST_F(LayerLifecycleManagerTest, rootLayerWithoutHandleIsDestroyed) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    layers.emplace_back(rootLayer(2));
+    lifecycleManager.addLayers(std::move(layers));
+    lifecycleManager.onHandlesDestroyed({1});
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2});
+    listener->expectLayersDestroyed({1});
+}
+
+TEST_F(LayerLifecycleManagerTest, offscreenLayerIsDestroyed) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    layers.emplace_back(rootLayer(2));
+    layers.emplace_back(childLayer(3, /*parent*/ 2));
+    lifecycleManager.addLayers(std::move(layers));
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2, 3});
+    listener->expectLayersDestroyed({});
+
+    lifecycleManager.applyTransactions({reparentLayer(3, UNASSIGNED_LAYER_ID)});
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({});
+    listener->expectLayersDestroyed({});
+
+    lifecycleManager.onHandlesDestroyed({3});
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({});
+    listener->expectLayersDestroyed({3});
+}
+
+TEST_F(LayerLifecycleManagerTest, offscreenChildLayerWithHandleIsNotDestroyed) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    layers.emplace_back(rootLayer(2));
+    layers.emplace_back(childLayer(3, /*parent*/ 2));
+    layers.emplace_back(childLayer(4, /*parent*/ 3));
+    lifecycleManager.addLayers(std::move(layers));
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2, 3, 4});
+    listener->expectLayersDestroyed({});
+
+    lifecycleManager.applyTransactions({reparentLayer(3, UNASSIGNED_LAYER_ID)});
+    lifecycleManager.onHandlesDestroyed({3});
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({});
+    listener->expectLayersDestroyed({3});
+}
+
+TEST_F(LayerLifecycleManagerTest, offscreenChildLayerWithoutHandleIsDestroyed) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    layers.emplace_back(rootLayer(2));
+    layers.emplace_back(childLayer(3, /*parent*/ 2));
+    layers.emplace_back(childLayer(4, /*parent*/ 3));
+    lifecycleManager.addLayers(std::move(layers));
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2, 3, 4});
+    listener->expectLayersDestroyed({});
+
+    lifecycleManager.applyTransactions({reparentLayer(3, UNASSIGNED_LAYER_ID)});
+    lifecycleManager.onHandlesDestroyed({3, 4});
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({});
+    listener->expectLayersDestroyed({3, 4});
+}
+
+TEST_F(LayerLifecycleManagerTest, reparentingDoesNotAffectRelativeZ) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    layers.emplace_back(rootLayer(2));
+    layers.emplace_back(childLayer(3, /*parent*/ 2));
+    layers.emplace_back(childLayer(4, /*parent*/ 3));
+
+    lifecycleManager.addLayers(std::move(layers));
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2, 3, 4});
+    listener->expectLayersDestroyed({});
+
+    lifecycleManager.applyTransactions({makeRelative(4, 1)});
+    EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+    lifecycleManager.applyTransactions({reparentLayer(4, 2)});
+    EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({});
+    listener->expectLayersDestroyed({});
+}
+
+TEST_F(LayerLifecycleManagerTest, reparentingToNullRemovesRelativeZ) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    layers.emplace_back(rootLayer(2));
+    layers.emplace_back(childLayer(3, /*parent*/ 2));
+    layers.emplace_back(childLayer(4, /*parent*/ 3));
+
+    lifecycleManager.addLayers(std::move(layers));
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2, 3, 4});
+    listener->expectLayersDestroyed({});
+
+    lifecycleManager.applyTransactions({makeRelative(4, 1)});
+    EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+    lifecycleManager.applyTransactions({reparentLayer(4, UNASSIGNED_LAYER_ID)});
+    EXPECT_FALSE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({});
+    listener->expectLayersDestroyed({});
+}
+
+TEST_F(LayerLifecycleManagerTest, setZRemovesRelativeZ) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    layers.emplace_back(rootLayer(2));
+    layers.emplace_back(childLayer(3, /*parent*/ 2));
+    layers.emplace_back(childLayer(4, /*parent*/ 3));
+
+    lifecycleManager.addLayers(std::move(layers));
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2, 3, 4});
+    listener->expectLayersDestroyed({});
+
+    lifecycleManager.applyTransactions({makeRelative(4, 1)});
+    EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+    lifecycleManager.applyTransactions({setLayer(4, 1)});
+    EXPECT_FALSE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({});
+    listener->expectLayersDestroyed({});
+}
+
+TEST_F(LayerLifecycleManagerTest, canAddBackgroundLayer) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    lifecycleManager.addLayers(std::move(layers));
+
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.bgColorAlpha = 0.5;
+    transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+    sp<LayerHandle> handle = sp<LayerHandle>::make(1u);
+    transactions.back().states.front().state.surface = handle;
+    lifecycleManager.applyTransactions(transactions);
+
+    auto& managedLayers = lifecycleManager.getLayers();
+    ASSERT_EQ(managedLayers.size(), 2u);
+
+    EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2});
+    listener->expectLayersDestroyed({});
+    EXPECT_EQ(getRequestedLayerState(lifecycleManager, 2)->color.a, 0.5_hf);
+}
+
+TEST_F(LayerLifecycleManagerTest, canDestroyBackgroundLayer) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    lifecycleManager.addLayers(std::move(layers));
+
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.bgColorAlpha = 0.5;
+    transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+    sp<LayerHandle> handle = sp<LayerHandle>::make(1u);
+    transactions.back().states.front().state.surface = handle;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.bgColorAlpha = 0;
+    transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+    transactions.back().states.front().state.surface = handle;
+
+    lifecycleManager.applyTransactions(transactions);
+
+    ASSERT_EQ(lifecycleManager.getLayers().size(), 1u);
+    ASSERT_EQ(lifecycleManager.getDestroyedLayers().size(), 1u);
+
+    EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2});
+    listener->expectLayersDestroyed({2});
+}
+
+TEST_F(LayerLifecycleManagerTest, onParentDestroyDestroysBackgroundLayer) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    lifecycleManager.addLayers(std::move(layers));
+
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.bgColorAlpha = 0.5;
+    transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+    sp<LayerHandle> handle = sp<LayerHandle>::make(1u);
+    transactions.back().states.front().state.surface = handle;
+    transactions.emplace_back();
+    lifecycleManager.applyTransactions(transactions);
+    lifecycleManager.onHandlesDestroyed({1});
+
+    ASSERT_EQ(lifecycleManager.getLayers().size(), 0u);
+    ASSERT_EQ(lifecycleManager.getDestroyedLayers().size(), 2u);
+
+    EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2});
+    listener->expectLayersDestroyed({1, 2});
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index cedb7eb..8757e63 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -26,6 +26,7 @@
 #include <log/log.h>
 #include <ui/Size.h>
 
+#include <scheduler/FrameRateMode.h>
 #include "DisplayHardware/HWC2.h"
 #include "FpsOps.h"
 #include "Scheduler/RefreshRateSelector.h"
@@ -44,9 +45,13 @@
 
 using mock::createDisplayMode;
 
+// Use a C style macro to keep the line numbers printed in gtest
+#define EXPECT_SCORED_FRAME_RATE(modePtr, fps, scored) \
+    EXPECT_EQ((FrameRateMode{(fps), (modePtr)}), (scored).frameRateMode)
+
 struct TestableRefreshRateSelector : RefreshRateSelector {
+    using RefreshRateSelector::FrameRateRanking;
     using RefreshRateSelector::RefreshRateOrder;
-    using RefreshRateSelector::RefreshRateRanking;
 
     using RefreshRateSelector::RefreshRateSelector;
 
@@ -80,36 +85,41 @@
         return getMaxRefreshRateByPolicyLocked(getActiveModeItLocked()->second->getGroup());
     }
 
-    RefreshRateRanking rankRefreshRates(std::optional<int> anchorGroupOpt,
-                                        RefreshRateOrder refreshRateOrder) const {
+    FrameRateRanking rankRefreshRates(std::optional<int> anchorGroupOpt,
+                                      RefreshRateOrder refreshRateOrder) const {
         std::lock_guard lock(mLock);
-        return RefreshRateSelector::rankRefreshRates(anchorGroupOpt, refreshRateOrder);
+        return RefreshRateSelector::rankFrameRates(anchorGroupOpt, refreshRateOrder);
     }
 
     const std::vector<Fps>& knownFrameRates() const { return mKnownFrameRates; }
 
-    using RefreshRateSelector::GetRankedRefreshRatesCache;
-    auto& mutableGetRankedRefreshRatesCache() { return mGetRankedRefreshRatesCache; }
+    using RefreshRateSelector::GetRankedFrameRatesCache;
+    auto& mutableGetRankedRefreshRatesCache() { return mGetRankedFrameRatesCache; }
 
-    auto getRankedRefreshRates(const std::vector<LayerRequirement>& layers,
-                               GlobalSignals signals) const {
-        const auto result = RefreshRateSelector::getRankedRefreshRates(layers, signals);
+    auto getRankedFrameRates(const std::vector<LayerRequirement>& layers,
+                             GlobalSignals signals) const {
+        const auto result = RefreshRateSelector::getRankedFrameRates(layers, signals);
 
         EXPECT_TRUE(std::is_sorted(result.ranking.begin(), result.ranking.end(),
-                                   ScoredRefreshRate::DescendingScore{}));
+                                   ScoredFrameRate::DescendingScore{}));
 
         return result;
     }
 
     auto getRankedRefreshRatesAsPair(const std::vector<LayerRequirement>& layers,
                                      GlobalSignals signals) const {
-        const auto [ranking, consideredSignals] = getRankedRefreshRates(layers, signals);
+        const auto [ranking, consideredSignals] = getRankedFrameRates(layers, signals);
         return std::make_pair(ranking, consideredSignals);
     }
 
-    DisplayModePtr getBestRefreshRate(const std::vector<LayerRequirement>& layers = {},
-                                      GlobalSignals signals = {}) const {
-        return getRankedRefreshRates(layers, signals).ranking.front().modePtr;
+    DisplayModePtr getBestFrameRateMode(const std::vector<LayerRequirement>& layers = {},
+                                        GlobalSignals signals = {}) const {
+        return getRankedFrameRates(layers, signals).ranking.front().frameRateMode.modePtr;
+    }
+
+    ScoredFrameRate getBestScoredFrameRate(const std::vector<LayerRequirement>& layers = {},
+                                           GlobalSignals signals = {}) const {
+        return getRankedFrameRates(layers, signals).ranking.front();
     }
 
     SetPolicyResult setPolicy(const PolicyVariant& policy) {
@@ -120,9 +130,11 @@
     SetPolicyResult setDisplayManagerPolicy(const DisplayManagerPolicy& policy) {
         return setPolicy(policy);
     }
+
+    const auto& getPrimaryFrameRates() const { return mPrimaryFrameRates; }
 };
 
-class RefreshRateSelectorTest : public testing::Test {
+class RefreshRateSelectorTest : public testing::TestWithParam<Config::FrameRateOverride> {
 protected:
     using RefreshRateOrder = TestableRefreshRateSelector::RefreshRateOrder;
 
@@ -140,6 +152,7 @@
     static constexpr DisplayModeId kModeId24Frac{8};
     static constexpr DisplayModeId kModeId30Frac{9};
     static constexpr DisplayModeId kModeId60Frac{10};
+    static constexpr DisplayModeId kModeId35{11};
 
     static inline const DisplayModePtr kMode60 = createDisplayMode(kModeId60, 60_Hz);
     static inline const DisplayModePtr kMode60Frac = createDisplayMode(kModeId60Frac, 59.94_Hz);
@@ -156,12 +169,14 @@
     static inline const DisplayModePtr kMode30Frac = createDisplayMode(kModeId30Frac, 29.97_Hz);
     static inline const DisplayModePtr kMode25 = createDisplayMode(kModeId25, 25_Hz);
     static inline const DisplayModePtr kMode25_G1 = createDisplayMode(kModeId25, 25_Hz, 1);
+    static inline const DisplayModePtr kMode35 = createDisplayMode(kModeId35, 35_Hz);
     static inline const DisplayModePtr kMode50 = createDisplayMode(kModeId50, 50_Hz);
     static inline const DisplayModePtr kMode24 = createDisplayMode(kModeId24, 24_Hz);
     static inline const DisplayModePtr kMode24Frac = createDisplayMode(kModeId24Frac, 23.976_Hz);
 
     // Test configurations.
     static inline const DisplayModes kModes_60 = makeModes(kMode60);
+    static inline const DisplayModes kModes_35_60_90 = makeModes(kMode35, kMode60, kMode90);
     static inline const DisplayModes kModes_60_90 = makeModes(kMode60, kMode90);
     static inline const DisplayModes kModes_60_90_G1 = makeModes(kMode60, kMode90_G1);
     static inline const DisplayModes kModes_60_90_4K = makeModes(kMode60, kMode90_4K);
@@ -185,6 +200,13 @@
     static inline const DisplayModes kModes_24_25_30_50_60_Frac =
             makeModes(kMode24, kMode24Frac, kMode25, kMode30, kMode30Frac, kMode50, kMode60,
                       kMode60Frac);
+
+    static TestableRefreshRateSelector createSelector(DisplayModes modes,
+                                                      DisplayModeId activeModeId,
+                                                      Config config = {}) {
+        config.enableFrameRateOverride = GetParam();
+        return TestableRefreshRateSelector(modes, activeModeId, config);
+    }
 };
 
 RefreshRateSelectorTest::RefreshRateSelectorTest() {
@@ -201,13 +223,23 @@
 
 namespace {
 
-TEST_F(RefreshRateSelectorTest, oneMode_canSwitch) {
-    RefreshRateSelector selector(kModes_60, kModeId60);
-    EXPECT_FALSE(selector.canSwitch());
+INSTANTIATE_TEST_SUITE_P(PerOverrideConfig, RefreshRateSelectorTest,
+                         testing::Values(Config::FrameRateOverride::Disabled,
+                                         Config::FrameRateOverride::AppOverrideNativeRefreshRates,
+                                         Config::FrameRateOverride::AppOverride,
+                                         Config::FrameRateOverride::Enabled));
+
+TEST_P(RefreshRateSelectorTest, oneMode_canSwitch) {
+    auto selector = createSelector(kModes_60, kModeId60);
+    if (GetParam() == Config::FrameRateOverride::Enabled) {
+        EXPECT_TRUE(selector.canSwitch());
+    } else {
+        EXPECT_FALSE(selector.canSwitch());
+    }
 }
 
-TEST_F(RefreshRateSelectorTest, invalidPolicy) {
-    TestableRefreshRateSelector selector(kModes_60, kModeId60);
+TEST_P(RefreshRateSelectorTest, invalidPolicy) {
+    auto selector = createSelector(kModes_60, kModeId60);
 
     EXPECT_EQ(SetPolicyResult::Invalid,
               selector.setDisplayManagerPolicy({DisplayModeId(10), {60_Hz, 60_Hz}}));
@@ -215,8 +247,8 @@
               selector.setDisplayManagerPolicy({kModeId60, {20_Hz, 40_Hz}}));
 }
 
-TEST_F(RefreshRateSelectorTest, unchangedPolicy) {
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, unchangedPolicy) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
 
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}));
@@ -236,8 +268,8 @@
               selector.setDisplayManagerPolicy({kModeId90, {30_Hz, 90_Hz}}));
 }
 
-TEST_F(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap) {
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
 
     const auto minRate = selector.getMinSupportedRefreshRate();
     const auto performanceRate = selector.getMaxSupportedRefreshRate();
@@ -252,8 +284,8 @@
     EXPECT_EQ(performanceRateByPolicy, performanceRate);
 }
 
-TEST_F(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap_differentGroups) {
-    TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+TEST_P(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap_differentGroups) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId60);
 
     const auto minRate = selector.getMinRefreshRateByPolicy();
     const auto performanceRate = selector.getMaxSupportedRefreshRate();
@@ -276,8 +308,8 @@
     EXPECT_EQ(kMode90_G1, performanceRate90);
 }
 
-TEST_F(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap_differentResolutions) {
-    TestableRefreshRateSelector selector(kModes_60_90_4K, kModeId60);
+TEST_P(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap_differentResolutions) {
+    auto selector = createSelector(kModes_60_90_4K, kModeId60);
 
     const auto minRate = selector.getMinRefreshRateByPolicy();
     const auto performanceRate = selector.getMaxSupportedRefreshRate();
@@ -300,8 +332,8 @@
     EXPECT_EQ(kMode90_4K, performanceRate90);
 }
 
-TEST_F(RefreshRateSelectorTest, twoModes_policyChange) {
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, twoModes_policyChange) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
 
     const auto minRate = selector.getMinRefreshRateByPolicy();
     const auto performanceRate = selector.getMaxRefreshRateByPolicy();
@@ -319,8 +351,8 @@
     EXPECT_EQ(kMode60, performanceRate60);
 }
 
-TEST_F(RefreshRateSelectorTest, twoModes_getActiveMode) {
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, twoModes_getActiveMode) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
     {
         const auto& mode = selector.getActiveMode();
         EXPECT_EQ(mode.getId(), kModeId60);
@@ -340,31 +372,31 @@
     }
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_noLayers) {
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_noLayers) {
     {
-        TestableRefreshRateSelector selector(kModes_60_72_90, kModeId72);
+        auto selector = createSelector(kModes_60_72_90, kModeId72);
 
         // If there are no layers we select the default frame rate, which is the max of the primary
         // range.
-        EXPECT_EQ(kMode90, selector.getBestRefreshRate());
+        EXPECT_EQ(kMode90, selector.getBestFrameRateMode());
 
         EXPECT_EQ(SetPolicyResult::Changed,
                   selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
-        EXPECT_EQ(kMode60, selector.getBestRefreshRate());
+        EXPECT_EQ(kMode60, selector.getBestFrameRateMode());
     }
     {
         // We select max even when this will cause a non-seamless switch.
-        TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+        auto selector = createSelector(kModes_60_90_G1, kModeId60);
         constexpr bool kAllowGroupSwitching = true;
         EXPECT_EQ(SetPolicyResult::Changed,
                   selector.setDisplayManagerPolicy(
                           {kModeId90, {0_Hz, 90_Hz}, kAllowGroupSwitching}));
-        EXPECT_EQ(kMode90_G1, selector.getBestRefreshRate());
+        EXPECT_EQ(kMode90_G1, selector.getBestFrameRateMode());
     }
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_exactDontChangeRefreshRateWhenNotInPolicy) {
-    TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId72);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_exactDontChangeRefreshRateWhenNotInPolicy) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId72);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     layers[0].vote = LayerVoteType::ExplicitExact;
@@ -372,188 +404,187 @@
 
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId72, {0_Hz, 90_Hz}}));
-    EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_60_90) {
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_60_90) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::Min;
     lr.name = "Min";
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.vote = LayerVoteType::Max;
     lr.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
     lr.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 45_Hz;
     lr.name = "45Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 30_Hz;
     lr.name = "30Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 24_Hz;
     lr.name = "24Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.name = "";
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}));
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId60, {0_Hz, 120_Hz}}));
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_multipleThreshold_60_90) {
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId60,
-                                         {.frameRateMultipleThreshold = 90});
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_multipleThreshold_60_90) {
+    auto selector = createSelector(kModes_60_90, kModeId60, {.frameRateMultipleThreshold = 90});
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::Min;
     lr.name = "Min";
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.vote = LayerVoteType::Max;
     lr.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
     lr.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 45_Hz;
     lr.name = "45Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 30_Hz;
     lr.name = "30Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 24_Hz;
     lr.name = "24Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_60_72_90) {
-    TestableRefreshRateSelector selector(kModes_60_72_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_60_72_90) {
+    auto selector = createSelector(kModes_60_72_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_30_60_72_90_120) {
-    TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60_72_90_120) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
@@ -563,23 +594,23 @@
     lr1.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 48_Hz;
     lr2.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 48_Hz;
     lr2.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_30_60_90_120_DifferentTypes) {
-    TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60_90_120_DifferentTypes) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
@@ -591,7 +622,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -599,7 +630,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -607,7 +638,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "60Hz ExplicitDefault";
-    EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -615,7 +646,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -623,7 +654,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -631,7 +662,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
@@ -639,7 +670,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -647,7 +678,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -655,12 +686,13 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.name = "90Hz ExplicitExactOrMultiple";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_30_60_90_120_DifferentTypes_multipleThreshold) {
-    TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60,
-                                         {.frameRateMultipleThreshold = 120});
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_30_60_90_120_DifferentTypes_multipleThreshold) {
+    auto selector =
+            createSelector(kModes_30_60_72_90_120, kModeId60, {.frameRateMultipleThreshold = 120});
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
@@ -673,7 +705,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -681,7 +713,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -689,7 +721,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "60Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -697,7 +729,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -705,7 +737,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -713,7 +745,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
@@ -721,7 +753,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -729,7 +761,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -737,14 +769,14 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.name = "90Hz ExplicitExactOrMultiple";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -752,7 +784,7 @@
     lr2.desiredRefreshRate = 120_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "120Hz ExplicitDefault";
-    EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -760,7 +792,7 @@
     lr2.desiredRefreshRate = 120_Hz;
     lr2.vote = LayerVoteType::ExplicitExact;
     lr2.name = "120Hz ExplicitExact";
-    EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 10_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -768,7 +800,7 @@
     lr2.desiredRefreshRate = 120_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "120Hz ExplicitExact";
-    EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
 
     lr1.desiredRefreshRate = 30_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -779,86 +811,86 @@
     lr3.vote = LayerVoteType::Heuristic;
     lr3.desiredRefreshRate = 120_Hz;
     lr3.name = "120Hz Heuristic";
-    EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_30_60) {
-    TestableRefreshRateSelector selector(kModes_30_60, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60) {
+    auto selector = createSelector(kModes_30_60, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode30, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode30, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_30_60_72_90) {
-    TestableRefreshRateSelector selector(kModes_30_60_72_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60_72_90) {
+    auto selector = createSelector(kModes_30_60_72_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::Min;
     lr.name = "Min";
-    EXPECT_EQ(kMode30, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
 
     lr.vote = LayerVoteType::Max;
     lr.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
     lr.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
 
     lr.desiredRefreshRate = 45_Hz;
     lr.name = "45Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
 
     lr.desiredRefreshRate = 30_Hz;
     lr.name = "30Hz Heuristic";
-    EXPECT_EQ(kMode30, selector.getBestRefreshRate(layers));
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
 
     lr.desiredRefreshRate = 24_Hz;
     lr.name = "24Hz Heuristic";
-    EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
 
     lr.desiredRefreshRate = 24_Hz;
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr.name = "24Hz ExplicitExactOrMultiple";
-    EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_PriorityTest) {
-    TestableRefreshRateSelector selector(kModes_30_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_PriorityTest) {
+    auto selector = createSelector(kModes_30_60_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
@@ -866,43 +898,43 @@
 
     lr1.vote = LayerVoteType::Min;
     lr2.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr1.vote = LayerVoteType::Min;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr1.vote = LayerVoteType::Min;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr1.vote = LayerVoteType::Max;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr1.vote = LayerVoteType::Max;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr1.vote = LayerVoteType::Heuristic;
     lr1.desiredRefreshRate = 15_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr1.vote = LayerVoteType::Heuristic;
     lr1.desiredRefreshRate = 30_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_24FpsVideo) {
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_24FpsVideo) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
@@ -910,15 +942,14 @@
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
         lr.desiredRefreshRate = Fps::fromValue(fps);
-        const auto mode = selector.getBestRefreshRate(layers);
+        const auto mode = selector.getBestFrameRateMode(layers);
         EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses "
                                  << to_string(mode->getFps());
     }
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_24FpsVideo_multipleThreshold_60_120) {
-    TestableRefreshRateSelector selector(kModes_60_120, kModeId60,
-                                         {.frameRateMultipleThreshold = 120});
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_24FpsVideo_multipleThreshold_60_120) {
+    auto selector = createSelector(kModes_60_120, kModeId60, {.frameRateMultipleThreshold = 120});
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
@@ -926,14 +957,14 @@
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
         lr.desiredRefreshRate = Fps::fromValue(fps);
-        const auto mode = selector.getBestRefreshRate(layers);
+        const auto mode = selector.getBestFrameRateMode(layers);
         EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses "
                                  << to_string(mode->getFps());
     }
 }
 
-TEST_F(RefreshRateSelectorTest, twoModes_getBestRefreshRate_Explicit) {
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, twoModes_getBestFrameRateMode_Explicit) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
@@ -943,23 +974,23 @@
     lr1.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 90_Hz;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr1.vote = LayerVoteType::ExplicitDefault;
     lr1.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr1.vote = LayerVoteType::Heuristic;
     lr1.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_75HzContent) {
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_75HzContent) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
@@ -967,14 +998,14 @@
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     for (float fps = 75.0f; fps < 100.0f; fps += 0.1f) {
         lr.desiredRefreshRate = Fps::fromValue(fps);
-        const auto mode = selector.getBestRefreshRate(layers, {});
+        const auto mode = selector.getBestFrameRateMode(layers, {});
         EXPECT_EQ(kMode90, mode) << lr.desiredRefreshRate << " chooses "
                                  << to_string(mode->getFps());
     }
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_Multiples) {
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_Multiples) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
@@ -986,7 +1017,7 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
@@ -994,14 +1025,14 @@
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 30_Hz;
@@ -1009,18 +1040,18 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 30_Hz;
     lr1.name = "30Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 }
 
-TEST_F(RefreshRateSelectorTest, scrollWhileWatching60fps_60_90) {
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, scrollWhileWatching60fps_60_90) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
@@ -1031,28 +1062,28 @@
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::NoVote;
     lr2.name = "NoVote";
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::NoVote;
     lr2.name = "NoVote";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     // The other layer starts to provide buffers
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -1061,49 +1092,70 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 }
 
-TEST_F(RefreshRateSelectorTest, getMaxRefreshRatesByPolicy) {
+TEST_P(RefreshRateSelectorTest, getMaxRefreshRatesByPolicy) {
     // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
     // different group.
-    TestableRefreshRateSelector selector(kModes_30_60_90, kModeId60);
-
+    auto selector = createSelector(kModes_30_60_90, kModeId60);
     const auto refreshRates = selector.rankRefreshRates(selector.getActiveMode().getGroup(),
                                                         RefreshRateOrder::Descending);
 
-    const std::array expectedRefreshRates = {kMode90, kMode60, kMode30};
+    const auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{90_Hz, kMode90}, {60_Hz, kMode60}, {30_Hz, kMode30}};
+            case Config::FrameRateOverride::Enabled:
+                return {{90_Hz, kMode90}, {60_Hz, kMode60}, {45_Hz, kMode90}, {30_Hz, kMode30}};
+        }
+    }();
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
 
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
-                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 }
 
-TEST_F(RefreshRateSelectorTest, getMinRefreshRatesByPolicy) {
+TEST_P(RefreshRateSelectorTest, getMinRefreshRatesByPolicy) {
     // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
     // different group.
-    TestableRefreshRateSelector selector(kModes_30_60_90, kModeId60);
+    auto selector = createSelector(kModes_30_60_90, kModeId60);
 
     const auto refreshRates = selector.rankRefreshRates(selector.getActiveMode().getGroup(),
                                                         RefreshRateOrder::Ascending);
 
-    const std::array expectedRefreshRates = {kMode30, kMode60, kMode90};
+    const auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}};
+            case Config::FrameRateOverride::Enabled:
+                return {{30_Hz, kMode30}, {45_Hz, kMode90}, {60_Hz, kMode60}, {90_Hz, kMode90}};
+        }
+    }();
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
 
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
-                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 }
 
-TEST_F(RefreshRateSelectorTest, getMinRefreshRatesByPolicyOutsideTheGroup) {
+TEST_P(RefreshRateSelectorTest, getMinRefreshRatesByPolicyOutsideTheGroup) {
     // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
     // different group.
-    TestableRefreshRateSelector selector(kModes_30_60_90, kModeId72);
+    auto selector = createSelector(kModes_30_60_90, kModeId72);
 
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}}));
@@ -1111,20 +1163,31 @@
     const auto refreshRates =
             selector.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Ascending);
 
-    const std::array expectedRefreshRates = {kMode30, kMode60, kMode90};
+    const auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}};
+            case Config::FrameRateOverride::Enabled:
+                return {{30_Hz, kMode30}, {45_Hz, kMode90}, {60_Hz, kMode60}, {90_Hz, kMode90}};
+        }
+    }();
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
 
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
-                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 }
 
-TEST_F(RefreshRateSelectorTest, getMaxRefreshRatesByPolicyOutsideTheGroup) {
+TEST_P(RefreshRateSelectorTest, getMaxRefreshRatesByPolicyOutsideTheGroup) {
     // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
     // different group.
-    TestableRefreshRateSelector selector(kModes_30_60_90, kModeId72);
+    auto selector = createSelector(kModes_30_60_90, kModeId72);
 
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}}));
@@ -1132,29 +1195,52 @@
     const auto refreshRates = selector.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt,
                                                         RefreshRateOrder::Descending);
 
-    const std::array expectedRefreshRates = {kMode90, kMode60, kMode30};
+    const auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{90_Hz, kMode90}, {60_Hz, kMode60}, {30_Hz, kMode30}};
+            case Config::FrameRateOverride::Enabled:
+                return {{90_Hz, kMode90}, {60_Hz, kMode60}, {45_Hz, kMode90}, {30_Hz, kMode30}};
+        }
+    }();
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
 
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
-                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 }
 
-TEST_F(RefreshRateSelectorTest, powerOnImminentConsidered) {
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
 
-    auto [refreshRates, signals] = selector.getRankedRefreshRates({}, {});
+    auto [refreshRates, signals] = selector.getRankedFrameRates({}, {});
     EXPECT_FALSE(signals.powerOnImminent);
 
-    std::array expectedRefreshRates = {kMode90, kMode60};
+    auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{90_Hz, kMode90}, {60_Hz, kMode60}};
+            case Config::FrameRateOverride::Enabled:
+                return {{90_Hz, kMode90}, {60_Hz, kMode60},   {45_Hz, kMode90},
+                        {30_Hz, kMode60}, {22.5_Hz, kMode90}, {20_Hz, kMode60}};
+        }
+    }();
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
 
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
-                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 
     std::tie(refreshRates, signals) =
@@ -1164,9 +1250,11 @@
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
 
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
-                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
@@ -1182,29 +1270,43 @@
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
 
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
-                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 
     std::tie(refreshRates, signals) =
             selector.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = false});
     EXPECT_FALSE(signals.powerOnImminent);
 
-    expectedRefreshRates = {kMode60, kMode90};
+    expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{60_Hz, kMode60}, {90_Hz, kMode90}};
+            case Config::FrameRateOverride::Enabled:
+                return {{60_Hz, kMode60}, {90_Hz, kMode90},   {45_Hz, kMode90},
+                        {30_Hz, kMode60}, {22.5_Hz, kMode90}, {20_Hz, kMode60}};
+        }
+    }();
     ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
 
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].modePtr)
-                << "Expected fps " << expectedRefreshRates[i]->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].modePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 }
 
-TEST_F(RefreshRateSelectorTest, touchConsidered) {
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, touchConsidered) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
 
-    auto [_, signals] = selector.getRankedRefreshRates({}, {});
+    auto [_, signals] = selector.getRankedFrameRates({}, {});
     EXPECT_FALSE(signals.touch);
 
     std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair({}, {.touch = true});
@@ -1251,8 +1353,8 @@
     EXPECT_FALSE(signals.touch);
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitDefault) {
-    TestableRefreshRateSelector selector(kModes_60_90_72_120, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitDefault) {
+    auto selector = createSelector(kModes_60_90_72_120, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
@@ -1284,57 +1386,57 @@
         ss << "ExplicitDefault " << desired;
         lr.name = ss.str();
 
-        EXPECT_EQ(expected, selector.getBestRefreshRate(layers)->getFps());
+        EXPECT_EQ(expected, selector.getBestFrameRateMode(layers)->getFps());
     }
 }
 
-TEST_F(RefreshRateSelectorTest,
-       getBestRefreshRate_ExplicitExactOrMultiple_WithFractionalRefreshRates) {
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_ExplicitExactOrMultiple_WithFractionalRefreshRates) {
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     // Test that 23.976 will choose 24 if 23.976 is not supported
     {
-        TestableRefreshRateSelector selector(makeModes(kMode24, kMode25, kMode30, kMode30Frac,
-                                                       kMode60, kMode60Frac),
-                                             kModeId60);
+        auto selector = createSelector(makeModes(kMode24, kMode25, kMode30, kMode30Frac, kMode60,
+                                                 kMode60Frac),
+                                       kModeId60);
 
         lr.vote = LayerVoteType::ExplicitExactOrMultiple;
         lr.desiredRefreshRate = 23.976_Hz;
         lr.name = "ExplicitExactOrMultiple 23.976 Hz";
-        EXPECT_EQ(kModeId24, selector.getBestRefreshRate(layers)->getId());
+        EXPECT_EQ(kModeId24, selector.getBestFrameRateMode(layers)->getId());
     }
 
     // Test that 24 will choose 23.976 if 24 is not supported
     {
-        TestableRefreshRateSelector selector(makeModes(kMode24Frac, kMode25, kMode30, kMode30Frac,
-                                                       kMode60, kMode60Frac),
-                                             kModeId60);
+        auto selector = createSelector(makeModes(kMode24Frac, kMode25, kMode30, kMode30Frac,
+                                                 kMode60, kMode60Frac),
+                                       kModeId60);
 
         lr.desiredRefreshRate = 24_Hz;
         lr.name = "ExplicitExactOrMultiple 24 Hz";
-        EXPECT_EQ(kModeId24Frac, selector.getBestRefreshRate(layers)->getId());
+        EXPECT_EQ(kModeId24Frac, selector.getBestFrameRateMode(layers)->getId());
     }
 
     // Test that 29.97 will prefer 59.94 over 60 and 30
     {
-        TestableRefreshRateSelector selector(makeModes(kMode24, kMode24Frac, kMode25, kMode30,
-                                                       kMode60, kMode60Frac),
-                                             kModeId60);
+        auto selector = createSelector(makeModes(kMode24, kMode24Frac, kMode25, kMode30, kMode60,
+                                                 kMode60Frac),
+                                       kModeId60);
 
         lr.desiredRefreshRate = 29.97_Hz;
         lr.name = "ExplicitExactOrMultiple 29.97 Hz";
-        EXPECT_EQ(kModeId60Frac, selector.getBestRefreshRate(layers)->getId());
+        EXPECT_EQ(kModeId60Frac, selector.getBestFrameRateMode(layers)->getId());
     }
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExact_WithFractionalRefreshRates) {
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitExact_WithFractionalRefreshRates) {
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     // Test that voting for supported refresh rate will select this refresh rate
     {
-        TestableRefreshRateSelector selector(kModes_24_25_30_50_60_Frac, kModeId60);
+        auto selector = createSelector(kModes_24_25_30_50_60_Frac, kModeId60);
 
         for (auto desired : {23.976_Hz, 24_Hz, 25_Hz, 29.97_Hz, 30_Hz, 50_Hz, 59.94_Hz, 60_Hz}) {
             lr.vote = LayerVoteType::ExplicitExact;
@@ -1343,14 +1445,14 @@
             ss << "ExplicitExact " << desired;
             lr.name = ss.str();
 
-            EXPECT_EQ(lr.desiredRefreshRate, selector.getBestRefreshRate(layers)->getFps());
+            EXPECT_EQ(lr.desiredRefreshRate, selector.getBestFrameRateMode(layers)->getFps());
         }
     }
 }
 
-TEST_F(RefreshRateSelectorTest,
-       getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresTouchFlag) {
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId90);
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_withDisplayManagerRequestingSingleRate_ignoresTouchFlag) {
+    auto selector = createSelector(kModes_60_90, kModeId90);
 
     constexpr FpsRange k90 = {90_Hz, 90_Hz};
     constexpr FpsRange k60_90 = {60_Hz, 90_Hz};
@@ -1365,16 +1467,16 @@
     lr.name = "60Hz ExplicitDefault";
     lr.focused = true;
 
-    const auto [mode, signals] =
-            selector.getRankedRefreshRates(layers, {.touch = true, .idle = true});
+    const auto [rankedFrameRate, signals] =
+            selector.getRankedFrameRates(layers, {.touch = true, .idle = true});
 
-    EXPECT_EQ(mode.begin()->modePtr, kMode60);
+    EXPECT_EQ(rankedFrameRate.begin()->frameRateMode.modePtr, kMode60);
     EXPECT_FALSE(signals.touch);
 }
 
-TEST_F(RefreshRateSelectorTest,
-       getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresIdleFlag) {
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_withDisplayManagerRequestingSingleRate_ignoresIdleFlag) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
 
     constexpr FpsRange k60 = {60_Hz, 60_Hz};
     constexpr FpsRange k60_90 = {60_Hz, 90_Hz};
@@ -1389,11 +1491,11 @@
     lr.desiredRefreshRate = 90_Hz;
     lr.name = "90Hz ExplicitDefault";
     lr.focused = true;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers, {.idle = true}));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.idle = true}));
 }
 
-TEST_F(RefreshRateSelectorTest, testDisplayModeOrdering) {
-    TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60);
+TEST_P(RefreshRateSelectorTest, testDisplayModeOrdering) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f},
                                             {.weight = 1.f},
@@ -1426,15 +1528,31 @@
     lr5.name = "30Hz";
     lr5.focused = true;
 
-    std::array expectedRanking = {kMode120, kMode90, kMode72, kMode60, kMode30};
-    auto actualRanking = selector.getRankedRefreshRates(layers, {}).ranking;
+    auto expectedRanking = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{120_Hz, kMode120},
+                        {90_Hz, kMode90},
+                        {72_Hz, kMode72},
+                        {60_Hz, kMode60},
+                        {30_Hz, kMode30}};
+            case Config::FrameRateOverride::Enabled:
+                return {{120_Hz, kMode120}, {90_Hz, kMode90},  {72_Hz, kMode72}, {60_Hz, kMode60},
+                        {45_Hz, kMode90},   {40_Hz, kMode120}, {36_Hz, kMode72}, {30_Hz, kMode30}};
+        }
+    }();
 
+    auto actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
     ASSERT_EQ(expectedRanking.size(), actualRanking.size());
 
     for (size_t i = 0; i < expectedRanking.size(); ++i) {
-        EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
-                << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
-                << actualRanking[i].modePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+                << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 
     lr1.vote = LayerVoteType::Max;
@@ -1452,15 +1570,31 @@
     lr5.desiredRefreshRate = 120_Hz;
     lr5.name = "120Hz";
 
-    expectedRanking = {kMode120, kMode90, kMode72, kMode60, kMode30};
-    actualRanking = selector.getRankedRefreshRates(layers, {}).ranking;
+    expectedRanking = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{120_Hz, kMode120},
+                        {90_Hz, kMode90},
+                        {72_Hz, kMode72},
+                        {60_Hz, kMode60},
+                        {30_Hz, kMode30}};
+            case Config::FrameRateOverride::Enabled:
+                return {{120_Hz, kMode120}, {90_Hz, kMode90},  {72_Hz, kMode72}, {60_Hz, kMode60},
+                        {45_Hz, kMode90},   {40_Hz, kMode120}, {36_Hz, kMode72}, {30_Hz, kMode30}};
+        }
+    }();
+    actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
 
     ASSERT_EQ(expectedRanking.size(), actualRanking.size());
 
     for (size_t i = 0; i < expectedRanking.size(); ++i) {
-        EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
-                << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
-                << actualRanking[i].modePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+                << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 
     lr1.vote = LayerVoteType::Heuristic;
@@ -1476,15 +1610,31 @@
     lr5.desiredRefreshRate = 72_Hz;
     lr5.name = "72Hz";
 
-    expectedRanking = {kMode30, kMode60, kMode90, kMode120, kMode72};
-    actualRanking = selector.getRankedRefreshRates(layers, {}).ranking;
+    expectedRanking = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{30_Hz, kMode30},
+                        {60_Hz, kMode60},
+                        {90_Hz, kMode90},
+                        {120_Hz, kMode120},
+                        {72_Hz, kMode72}};
+            case Config::FrameRateOverride::Enabled:
+                return {{30_Hz, kMode30}, {60_Hz, kMode60},  {90_Hz, kMode90}, {120_Hz, kMode120},
+                        {45_Hz, kMode90}, {40_Hz, kMode120}, {72_Hz, kMode72}, {36_Hz, kMode72}};
+        }
+    }();
+    actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
 
     ASSERT_EQ(expectedRanking.size(), actualRanking.size());
 
     for (size_t i = 0; i < expectedRanking.size(); ++i) {
-        EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
-                << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
-                << actualRanking[i].modePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+                << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 
     lr1.desiredRefreshRate = 120_Hz;
@@ -1503,21 +1653,37 @@
     lr5.desiredRefreshRate = 120_Hz;
     lr5.name = "120Hz-2";
 
-    expectedRanking = {kMode90, kMode60, kMode120, kMode72, kMode30};
-    actualRanking = selector.getRankedRefreshRates(layers, {}).ranking;
+    expectedRanking = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{90_Hz, kMode90},
+                        {60_Hz, kMode60},
+                        {120_Hz, kMode120},
+                        {72_Hz, kMode72},
+                        {30_Hz, kMode30}};
+            case Config::FrameRateOverride::Enabled:
+                return {{90_Hz, kMode90}, {60_Hz, kMode60},  {120_Hz, kMode120}, {72_Hz, kMode72},
+                        {45_Hz, kMode90}, {40_Hz, kMode120}, {36_Hz, kMode72},   {30_Hz, kMode30}};
+        }
+    }();
+    actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
 
     ASSERT_EQ(expectedRanking.size(), actualRanking.size());
 
     for (size_t i = 0; i < expectedRanking.size(); ++i) {
-        EXPECT_EQ(expectedRanking[i], actualRanking[i].modePtr)
-                << "Expected fps " << expectedRanking[i]->getFps().getIntValue() << " Actual fps "
-                << actualRanking[i].modePtr->getFps().getIntValue();
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+                << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 }
 
-TEST_F(RefreshRateSelectorTest,
-       getBestRefreshRate_withDisplayManagerRequestingSingleRate_onlySwitchesRatesForExplicitFocusedLayers) {
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId90);
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_withDisplayManagerRequestingSingleRate_onlySwitchesRatesForExplicitFocusedLayers) {
+    auto selector = createSelector(kModes_60_90, kModeId90);
 
     constexpr FpsRange k90 = {90_Hz, 90_Hz};
     constexpr FpsRange k60_90 = {60_Hz, 90_Hz};
@@ -1525,8 +1691,8 @@
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId90, {k90, k90}, {k60_90, k60_90}}));
 
-    const auto [ranking, signals] = selector.getRankedRefreshRates({}, {});
-    EXPECT_EQ(ranking.front().modePtr, kMode90);
+    const auto [ranking, signals] = selector.getRankedFrameRates({}, {});
+    EXPECT_EQ(ranking.front().frameRateMode.modePtr, kMode90);
     EXPECT_FALSE(signals.touch);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
@@ -1536,50 +1702,50 @@
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz ExplicitExactOrMultiple";
     lr.focused = false;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.focused = true;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.vote = LayerVoteType::ExplicitDefault;
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz ExplicitDefault";
     lr.focused = false;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.focused = true;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 
     lr.vote = LayerVoteType::Heuristic;
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
     lr.focused = false;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.focused = true;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.vote = LayerVoteType::Max;
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Max";
     lr.focused = false;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.focused = true;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.vote = LayerVoteType::Min;
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Min";
     lr.focused = false;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 
     lr.focused = true;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
 }
 
-TEST_F(RefreshRateSelectorTest, groupSwitchingNotAllowed) {
-    TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+TEST_P(RefreshRateSelectorTest, groupSwitchingNotAllowed) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId60);
 
     // The default policy doesn't allow group switching. Verify that no
     // group switches are performed.
@@ -1591,11 +1757,11 @@
     layer.name = "90Hz ExplicitDefault";
     layer.focused = true;
 
-    EXPECT_EQ(kModeId60, selector.getBestRefreshRate(layers)->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
 }
 
-TEST_F(RefreshRateSelectorTest, groupSwitchingWithOneLayer) {
-    TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayer) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId60);
 
     RefreshRateSelector::DisplayManagerPolicy policy;
     policy.defaultMode = selector.getCurrentPolicy().defaultMode;
@@ -1609,11 +1775,11 @@
     layer.seamlessness = Seamlessness::SeamedAndSeamless;
     layer.name = "90Hz ExplicitDefault";
     layer.focused = true;
-    EXPECT_EQ(kModeId90, selector.getBestRefreshRate(layers)->getId());
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
 }
 
-TEST_F(RefreshRateSelectorTest, groupSwitchingWithOneLayerOnlySeamless) {
-    TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerOnlySeamless) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId60);
 
     RefreshRateSelector::DisplayManagerPolicy policy;
     policy.defaultMode = selector.getCurrentPolicy().defaultMode;
@@ -1628,11 +1794,11 @@
     layer.seamlessness = Seamlessness::OnlySeamless;
     layer.name = "90Hz ExplicitDefault";
     layer.focused = true;
-    EXPECT_EQ(kModeId60, selector.getBestRefreshRate(layers)->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
 }
 
-TEST_F(RefreshRateSelectorTest, groupSwitchingWithOneLayerOnlySeamlessDefaultFps) {
-    TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerOnlySeamlessDefaultFps) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId60);
 
     RefreshRateSelector::DisplayManagerPolicy policy;
     policy.defaultMode = selector.getCurrentPolicy().defaultMode;
@@ -1649,11 +1815,11 @@
     layer.seamlessness = Seamlessness::OnlySeamless;
     layer.name = "60Hz ExplicitDefault";
     layer.focused = true;
-    EXPECT_EQ(kModeId90, selector.getBestRefreshRate(layers)->getId());
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
 }
 
-TEST_F(RefreshRateSelectorTest, groupSwitchingWithOneLayerDefaultSeamlessness) {
-    TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerDefaultSeamlessness) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId60);
 
     RefreshRateSelector::DisplayManagerPolicy policy;
     policy.defaultMode = selector.getCurrentPolicy().defaultMode;
@@ -1673,11 +1839,11 @@
     layer.name = "60Hz ExplicitDefault";
     layer.focused = true;
 
-    EXPECT_EQ(kModeId60, selector.getBestRefreshRate(layers)->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
 }
 
-TEST_F(RefreshRateSelectorTest, groupSwitchingWithTwoLayersOnlySeamlessAndSeamed) {
-    TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersOnlySeamlessAndSeamed) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId60);
 
     RefreshRateSelector::DisplayManagerPolicy policy;
     policy.defaultMode = selector.getCurrentPolicy().defaultMode;
@@ -1702,11 +1868,11 @@
     layers[1].name = "90Hz ExplicitDefault";
     layers[1].focused = false;
 
-    EXPECT_EQ(kModeId90, selector.getBestRefreshRate(layers)->getId());
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
 }
 
-TEST_F(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultFocusedAndSeamed) {
-    TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultFocusedAndSeamed) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId60);
 
     RefreshRateSelector::DisplayManagerPolicy policy;
     policy.defaultMode = selector.getCurrentPolicy().defaultMode;
@@ -1735,11 +1901,11 @@
     layers[1].vote = LayerVoteType::ExplicitDefault;
     layers[1].name = "90Hz ExplicitDefault";
 
-    EXPECT_EQ(kModeId90, selector.getBestRefreshRate(layers)->getId());
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
 }
 
-TEST_F(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultNotFocusedAndSeamed) {
-    TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId60);
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultNotFocusedAndSeamed) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId60);
 
     RefreshRateSelector::DisplayManagerPolicy policy;
     policy.defaultMode = selector.getCurrentPolicy().defaultMode;
@@ -1765,11 +1931,11 @@
     layers[1].vote = LayerVoteType::ExplicitDefault;
     layers[1].name = "90Hz ExplicitDefault";
 
-    EXPECT_EQ(kModeId60, selector.getBestRefreshRate(layers)->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
 }
 
-TEST_F(RefreshRateSelectorTest, nonSeamlessVotePrefersSeamlessSwitches) {
-    TestableRefreshRateSelector selector(kModes_30_60, kModeId60);
+TEST_P(RefreshRateSelectorTest, nonSeamlessVotePrefersSeamlessSwitches) {
+    auto selector = createSelector(kModes_30_60, kModeId60);
 
     // Allow group switching.
     RefreshRateSelector::DisplayManagerPolicy policy;
@@ -1785,14 +1951,14 @@
     layer.name = "60Hz ExplicitExactOrMultiple";
     layer.focused = true;
 
-    EXPECT_EQ(kModeId60, selector.getBestRefreshRate(layers)->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
 
     selector.setActiveModeId(kModeId120);
-    EXPECT_EQ(kModeId120, selector.getBestRefreshRate(layers)->getId());
+    EXPECT_EQ(kModeId120, selector.getBestFrameRateMode(layers)->getId());
 }
 
-TEST_F(RefreshRateSelectorTest, nonSeamlessExactAndSeamlessMultipleLayers) {
-    TestableRefreshRateSelector selector(kModes_25_30_50_60, kModeId60);
+TEST_P(RefreshRateSelectorTest, nonSeamlessExactAndSeamlessMultipleLayers) {
+    auto selector = createSelector(kModes_25_30_50_60, kModeId60);
 
     // Allow group switching.
     RefreshRateSelector::DisplayManagerPolicy policy;
@@ -1813,18 +1979,18 @@
                                              .weight = 1.f,
                                              .focused = true}};
 
-    EXPECT_EQ(kModeId50, selector.getBestRefreshRate(layers)->getId());
+    EXPECT_EQ(kModeId50, selector.getBestFrameRateMode(layers)->getId());
 
     auto& seamedLayer = layers[0];
     seamedLayer.desiredRefreshRate = 30_Hz;
     seamedLayer.name = "30Hz ExplicitDefault";
     selector.setActiveModeId(kModeId30);
 
-    EXPECT_EQ(kModeId25, selector.getBestRefreshRate(layers)->getId());
+    EXPECT_EQ(kModeId25, selector.getBestFrameRateMode(layers)->getId());
 }
 
-TEST_F(RefreshRateSelectorTest, minLayersDontTrigerSeamedSwitch) {
-    TestableRefreshRateSelector selector(kModes_60_90_G1, kModeId90);
+TEST_P(RefreshRateSelectorTest, minLayersDontTrigerSeamedSwitch) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId90);
 
     // Allow group switching.
     RefreshRateSelector::DisplayManagerPolicy policy;
@@ -1835,11 +2001,11 @@
     std::vector<LayerRequirement> layers = {
             {.name = "Min", .vote = LayerVoteType::Min, .weight = 1.f, .focused = true}};
 
-    EXPECT_EQ(kModeId90, selector.getBestRefreshRate(layers)->getId());
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
 }
 
-TEST_F(RefreshRateSelectorTest, primaryVsAppRequestPolicy) {
-    TestableRefreshRateSelector selector(kModes_30_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, primaryVsAppRequestPolicy) {
+    auto selector = createSelector(kModes_30_60_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     layers[0].name = "Test layer";
@@ -1849,13 +2015,14 @@
         bool focused = true;
     };
 
-    // Returns the mode selected by getBestRefreshRate for a single layer with the given arguments.
+    // Returns the mode selected by getBestFrameRateMode for a single layer with the given
+    // arguments.
     const auto getFrameRate = [&](LayerVoteType voteType, Fps fps,
                                   Args args = {}) -> DisplayModeId {
         layers[0].vote = voteType;
         layers[0].desiredRefreshRate = fps;
         layers[0].focused = args.focused;
-        return selector.getBestRefreshRate(layers, {.touch = args.touch})->getId();
+        return selector.getBestFrameRateMode(layers, {.touch = args.touch})->getId();
     };
 
     constexpr FpsRange k30_60 = {30_Hz, 60_Hz};
@@ -1864,7 +2031,7 @@
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId60, {k30_60, k30_60}, {k30_90, k30_90}}));
 
-    EXPECT_EQ(kModeId60, selector.getBestRefreshRate()->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode()->getId());
     EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz));
     EXPECT_EQ(kModeId30, getFrameRate(LayerVoteType::Min, 90_Hz));
     EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz));
@@ -1897,22 +2064,23 @@
     EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz));
 }
 
-TEST_F(RefreshRateSelectorTest, idle) {
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, idle) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     layers[0].name = "Test layer";
 
-    const auto getIdleFrameRate = [&](LayerVoteType voteType, bool touchActive) -> DisplayModeId {
+    const auto getIdleDisplayModeId = [&](LayerVoteType voteType,
+                                          bool touchActive) -> DisplayModeId {
         layers[0].vote = voteType;
         layers[0].desiredRefreshRate = 90_Hz;
 
         const auto [ranking, signals] =
-                selector.getRankedRefreshRates(layers, {.touch = touchActive, .idle = true});
+                selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true});
 
         // Refresh rate will be chosen by either touch state or idle state.
         EXPECT_EQ(!touchActive, signals.idle);
-        return ranking.front().modePtr->getId();
+        return ranking.front().frameRateMode.modePtr->getId();
     };
 
     EXPECT_EQ(SetPolicyResult::Changed,
@@ -1921,38 +2089,38 @@
     // Idle should be lower priority than touch boost.
     {
         constexpr bool kTouchActive = true;
-        EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::NoVote, kTouchActive));
-        EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::Min, kTouchActive));
-        EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::Max, kTouchActive));
-        EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::Heuristic, kTouchActive));
-        EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::ExplicitDefault, kTouchActive));
+        EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::NoVote, kTouchActive));
+        EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::Min, kTouchActive));
+        EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::Max, kTouchActive));
+        EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::Heuristic, kTouchActive));
+        EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::ExplicitDefault, kTouchActive));
         EXPECT_EQ(kModeId90,
-                  getIdleFrameRate(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
+                  getIdleDisplayModeId(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
     }
 
     // With no layers, idle should still be lower priority than touch boost.
-    EXPECT_EQ(kModeId90, selector.getBestRefreshRate({}, {.touch = true, .idle = true})->getId());
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode({}, {.touch = true, .idle = true})->getId());
 
     // Idle should be higher precedence than other layer frame rate considerations.
     selector.setActiveModeId(kModeId90);
 
     {
         constexpr bool kTouchActive = false;
-        EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::NoVote, kTouchActive));
-        EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::Min, kTouchActive));
-        EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::Max, kTouchActive));
-        EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::Heuristic, kTouchActive));
-        EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::ExplicitDefault, kTouchActive));
+        EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::NoVote, kTouchActive));
+        EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::Min, kTouchActive));
+        EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::Max, kTouchActive));
+        EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::Heuristic, kTouchActive));
+        EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::ExplicitDefault, kTouchActive));
         EXPECT_EQ(kModeId60,
-                  getIdleFrameRate(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
+                  getIdleDisplayModeId(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
     }
 
     // Idle should be applied rather than the active mode when there are no layers.
-    EXPECT_EQ(kModeId60, selector.getBestRefreshRate({}, {.idle = true})->getId());
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode({}, {.idle = true})->getId());
 }
 
-TEST_F(RefreshRateSelectorTest, findClosestKnownFrameRate) {
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, findClosestKnownFrameRate) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
 
     for (float fps = 1.0f; fps <= 120.0f; fps += 0.1f) {
         const auto knownFrameRate = selector.findClosestKnownFrameRate(Fps::fromValue(fps));
@@ -1969,8 +2137,8 @@
     }
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_KnownFrameRate) {
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_KnownFrameRate) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
 
     struct Expectation {
         Fps fps;
@@ -1997,101 +2165,80 @@
 
     for (const auto& [fps, mode] : knownFrameRatesExpectations) {
         layer.desiredRefreshRate = fps;
-        EXPECT_EQ(mode, selector.getBestRefreshRate(layers));
+        EXPECT_EQ(mode, selector.getBestFrameRateMode(layers));
     }
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExact) {
-    TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitExact) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
     auto& explicitExactLayer = layers[0];
     auto& explicitExactOrMultipleLayer = layers[1];
 
-    explicitExactOrMultipleLayer.vote = LayerVoteType::ExplicitExactOrMultiple;
-    explicitExactOrMultipleLayer.name = "ExplicitExactOrMultiple";
-    explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
-
     explicitExactLayer.vote = LayerVoteType::ExplicitExact;
     explicitExactLayer.name = "ExplicitExact";
     explicitExactLayer.desiredRefreshRate = 30_Hz;
 
-    EXPECT_EQ(kMode30, selector.getBestRefreshRate(layers));
-    EXPECT_EQ(kMode30, selector.getBestRefreshRate(layers, {.touch = true}));
-
-    explicitExactOrMultipleLayer.desiredRefreshRate = 120_Hz;
-    explicitExactLayer.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
-
-    explicitExactLayer.desiredRefreshRate = 72_Hz;
-    EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
-
-    explicitExactLayer.desiredRefreshRate = 90_Hz;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
-
-    explicitExactLayer.desiredRefreshRate = 120_Hz;
-    EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExactEnableFrameRateOverride) {
-    TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60,
-                                         {.enableFrameRateOverride =
-                                                  Config::FrameRateOverride::Enabled});
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
-    auto& explicitExactLayer = layers[0];
-    auto& explicitExactOrMultipleLayer = layers[1];
-
     explicitExactOrMultipleLayer.vote = LayerVoteType::ExplicitExactOrMultiple;
     explicitExactOrMultipleLayer.name = "ExplicitExactOrMultiple";
     explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
 
-    explicitExactLayer.vote = LayerVoteType::ExplicitExact;
-    explicitExactLayer.name = "ExplicitExact";
-    explicitExactLayer.desiredRefreshRate = 30_Hz;
+    if (GetParam() == Config::FrameRateOverride::Disabled) {
+        EXPECT_SCORED_FRAME_RATE(kMode30, 30_Hz, selector.getBestScoredFrameRate(layers));
+        EXPECT_SCORED_FRAME_RATE(kMode30, 30_Hz,
+                                 selector.getBestScoredFrameRate(layers, {.touch = true}));
 
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
-    EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers, {.touch = true}));
+    } else {
+        EXPECT_SCORED_FRAME_RATE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers));
+        EXPECT_SCORED_FRAME_RATE(kMode120, 120_Hz,
+                                 selector.getBestScoredFrameRate(layers, {.touch = true}));
+    }
 
     explicitExactOrMultipleLayer.desiredRefreshRate = 120_Hz;
     explicitExactLayer.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+
+    if (GetParam() == Config::FrameRateOverride::Disabled) {
+        EXPECT_SCORED_FRAME_RATE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers));
+    } else {
+        EXPECT_SCORED_FRAME_RATE(kMode120, 120_Hz, selector.getBestScoredFrameRate(layers));
+    }
 
     explicitExactLayer.desiredRefreshRate = 72_Hz;
-    EXPECT_EQ(kMode72, selector.getBestRefreshRate(layers));
+    EXPECT_SCORED_FRAME_RATE(kMode72, 72_Hz, selector.getBestScoredFrameRate(layers));
 
     explicitExactLayer.desiredRefreshRate = 90_Hz;
-    EXPECT_EQ(kMode90, selector.getBestRefreshRate(layers));
+    EXPECT_SCORED_FRAME_RATE(kMode90, 90_Hz, selector.getBestScoredFrameRate(layers));
 
     explicitExactLayer.desiredRefreshRate = 120_Hz;
-    EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers));
+    EXPECT_SCORED_FRAME_RATE(kMode120, 120_Hz, selector.getBestScoredFrameRate(layers));
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ReadsCache) {
-    TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ReadsCache) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
 
     using GlobalSignals = RefreshRateSelector::GlobalSignals;
     const auto args = std::make_pair(std::vector<LayerRequirement>{},
                                      GlobalSignals{.touch = true, .idle = true});
 
-    const RefreshRateSelector::RankedRefreshRates result = {{RefreshRateSelector::ScoredRefreshRate{
-                                                                    kMode90}},
-                                                            {.touch = true}};
+    const RefreshRateSelector::RankedFrameRates result = {{RefreshRateSelector::ScoredFrameRate{
+                                                                  {90_Hz, kMode90}}},
+                                                          GlobalSignals{.touch = true}};
 
     selector.mutableGetRankedRefreshRatesCache() = {args, result};
 
-    EXPECT_EQ(result, selector.getRankedRefreshRates(args.first, args.second));
+    EXPECT_EQ(result, selector.getRankedFrameRates(args.first, args.second));
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_WritesCache) {
-    TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId60);
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_WritesCache) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
 
     EXPECT_FALSE(selector.mutableGetRankedRefreshRatesCache());
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
     RefreshRateSelector::GlobalSignals globalSignals{.touch = true, .idle = true};
 
-    const auto result = selector.getRankedRefreshRates(layers, globalSignals);
+    const auto result = selector.getRankedFrameRates(layers, globalSignals);
 
     const auto& cache = selector.mutableGetRankedRefreshRatesCache();
     ASSERT_TRUE(cache);
@@ -2100,10 +2247,8 @@
     EXPECT_EQ(cache->result, result);
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_ExplicitExactTouchBoost) {
-    TestableRefreshRateSelector selector(kModes_60_120, kModeId60,
-                                         {.enableFrameRateOverride =
-                                                  Config::FrameRateOverride::Enabled});
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitExactTouchBoost) {
+    auto selector = createSelector(kModes_60_120, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
     auto& explicitExactLayer = layers[0];
@@ -2117,19 +2262,21 @@
     explicitExactLayer.name = "ExplicitExact";
     explicitExactLayer.desiredRefreshRate = 30_Hz;
 
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
-    EXPECT_EQ(kMode120, selector.getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    if (GetParam() == Config::FrameRateOverride::Disabled) {
+        EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers, {.touch = true}));
+    } else {
+        EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers, {.touch = true}));
+    }
 
     explicitExactOrMultipleLayer.vote = LayerVoteType::NoVote;
 
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers, {.touch = true}));
 }
 
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_FractionalRefreshRates_ExactAndDefault) {
-    TestableRefreshRateSelector selector(kModes_24_25_30_50_60_Frac, kModeId60,
-                                         {.enableFrameRateOverride =
-                                                  Config::FrameRateOverride::Enabled});
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_FractionalRefreshRates_ExactAndDefault) {
+    auto selector = createSelector(kModes_24_25_30_50_60_Frac, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 0.5f}, {.weight = 0.5f}};
     auto& explicitDefaultLayer = layers[0];
@@ -2143,12 +2290,12 @@
     explicitDefaultLayer.name = "ExplicitDefault";
     explicitDefaultLayer.desiredRefreshRate = 59.94_Hz;
 
-    EXPECT_EQ(kMode60, selector.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
 }
 
 // b/190578904
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_withCloseRefreshRates) {
-    constexpr int kMinRefreshRate = 10;
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withCloseRefreshRates) {
+    const int kMinRefreshRate = RefreshRateSelector::kMinSupportedFrameRate.getIntValue();
     constexpr int kMaxRefreshRate = 240;
 
     DisplayModes displayModes;
@@ -2159,14 +2306,13 @@
                                                    Fps::fromValue(static_cast<float>(fps))));
     }
 
-    const TestableRefreshRateSelector selector(std::move(displayModes),
-                                               DisplayModeId(kMinRefreshRate));
+    const auto selector = createSelector(std::move(displayModes), DisplayModeId(kMinRefreshRate));
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) {
         layers[0].desiredRefreshRate = fps;
         layers[0].vote = vote;
-        EXPECT_EQ(fps.getIntValue(), selector.getBestRefreshRate(layers)->getFps().getIntValue())
+        EXPECT_EQ(fps.getIntValue(), selector.getBestFrameRateMode(layers)->getFps().getIntValue())
                 << "Failed for " << ftl::enum_string(vote);
     };
 
@@ -2180,7 +2326,7 @@
 }
 
 // b/190578904
-TEST_F(RefreshRateSelectorTest, getBestRefreshRate_conflictingVotes) {
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_conflictingVotes) {
     constexpr DisplayModeId kActiveModeId{0};
     DisplayModes displayModes = makeModes(createDisplayMode(kActiveModeId, 43_Hz),
                                           createDisplayMode(DisplayModeId(1), 53_Hz),
@@ -2188,7 +2334,7 @@
                                           createDisplayMode(DisplayModeId(3), 60_Hz));
 
     const RefreshRateSelector::GlobalSignals globalSignals = {.touch = false, .idle = false};
-    const TestableRefreshRateSelector selector(std::move(displayModes), kActiveModeId);
+    const auto selector = createSelector(std::move(displayModes), kActiveModeId);
 
     const std::vector<LayerRequirement> layers = {
             {
@@ -2205,19 +2351,19 @@
             },
     };
 
-    EXPECT_EQ(53_Hz, selector.getBestRefreshRate(layers, globalSignals)->getFps());
+    EXPECT_EQ(53_Hz, selector.getBestFrameRateMode(layers, globalSignals)->getFps());
 }
 
-TEST_F(RefreshRateSelectorTest, modeComparison) {
+TEST_P(RefreshRateSelectorTest, modeComparison) {
     EXPECT_LT(kMode60->getFps(), kMode90->getFps());
     EXPECT_GE(kMode60->getFps(), kMode60->getFps());
     EXPECT_GE(kMode90->getFps(), kMode90->getFps());
 }
 
-TEST_F(RefreshRateSelectorTest, testKernelIdleTimerAction) {
+TEST_P(RefreshRateSelectorTest, testKernelIdleTimerAction) {
     using KernelIdleTimerAction = RefreshRateSelector::KernelIdleTimerAction;
 
-    TestableRefreshRateSelector selector(kModes_60_90, kModeId90);
+    auto selector = createSelector(kModes_60_90, kModeId90);
 
     EXPECT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction());
 
@@ -2234,10 +2380,10 @@
     EXPECT_EQ(KernelIdleTimerAction::TurnOff, selector.getIdleTimerAction());
 }
 
-TEST_F(RefreshRateSelectorTest, testKernelIdleTimerActionFor120Hz) {
+TEST_P(RefreshRateSelectorTest, testKernelIdleTimerActionFor120Hz) {
     using KernelIdleTimerAction = RefreshRateSelector::KernelIdleTimerAction;
 
-    TestableRefreshRateSelector selector(kModes_60_120, kModeId120);
+    auto selector = createSelector(kModes_60_120, kModeId120);
 
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId60, {0_Hz, 60_Hz}}));
@@ -2256,8 +2402,8 @@
     EXPECT_EQ(KernelIdleTimerAction::TurnOff, selector.getIdleTimerAction());
 }
 
-TEST_F(RefreshRateSelectorTest, getFrameRateDivisor) {
-    TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId30);
+TEST_P(RefreshRateSelectorTest, getFrameRateDivisor) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId30);
 
     const auto frameRate = 30_Hz;
     Fps displayRefreshRate = selector.getActiveMode().getFps();
@@ -2289,7 +2435,7 @@
     EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(60_Hz, 59.94_Hz));
 }
 
-TEST_F(RefreshRateSelectorTest, isFractionalPairOrMultiple) {
+TEST_P(RefreshRateSelectorTest, isFractionalPairOrMultiple) {
     EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(23.976_Hz, 24_Hz));
     EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(24_Hz, 23.976_Hz));
 
@@ -2315,22 +2461,72 @@
     EXPECT_FALSE(RefreshRateSelector::isFractionalPairOrMultiple(29.97_Hz, 59.94_Hz));
 }
 
-TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_noLayers) {
-    RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120);
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_noLayers) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
 
     EXPECT_TRUE(selector.getFrameRateOverrides({}, 120_Hz, {}).empty());
 }
 
-TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_60on120) {
-    RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120,
-                                 {.enableFrameRateOverride = Config::FrameRateOverride::Enabled});
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_NonExplicit) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     layers[0].name = "Test layer";
     layers[0].ownerUid = 1234;
     layers[0].desiredRefreshRate = 60_Hz;
-    layers[0].vote = LayerVoteType::ExplicitDefault;
 
+    layers[0].vote = LayerVoteType::NoVote;
+    EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+    layers[0].vote = LayerVoteType::Min;
+    EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+    layers[0].vote = LayerVoteType::Max;
+    EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+    layers[0].vote = LayerVoteType::Heuristic;
+    EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_Disabled) {
+    if (GetParam() != Config::FrameRateOverride::Disabled) {
+        return;
+    }
+
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].name = "Test layer";
+    layers[0].ownerUid = 1234;
+    layers[0].desiredRefreshRate = 60_Hz;
+
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+    EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+    layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+    EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+    layers[0].vote = LayerVoteType::ExplicitExact;
+    EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_60on120) {
+    if (GetParam() == Config::FrameRateOverride::Disabled) {
+        return;
+    }
+
+    ASSERT_TRUE(GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ||
+                GetParam() == Config::FrameRateOverride::AppOverride ||
+                GetParam() == Config::FrameRateOverride::Enabled);
+
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].name = "Test layer";
+    layers[0].ownerUid = 1234;
+    layers[0].desiredRefreshRate = 60_Hz;
+
+    layers[0].vote = LayerVoteType::ExplicitDefault;
     auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
     EXPECT_EQ(1u, frameRateOverrides.size());
     ASSERT_EQ(1u, frameRateOverrides.count(1234));
@@ -2342,26 +2538,23 @@
     ASSERT_EQ(1u, frameRateOverrides.count(1234));
     EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
 
-    layers[0].vote = LayerVoteType::NoVote;
+    layers[0].vote = LayerVoteType::ExplicitExact;
     frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_TRUE(frameRateOverrides.empty());
-
-    layers[0].vote = LayerVoteType::Min;
-    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_TRUE(frameRateOverrides.empty());
-
-    layers[0].vote = LayerVoteType::Max;
-    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_TRUE(frameRateOverrides.empty());
-
-    layers[0].vote = LayerVoteType::Heuristic;
-    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_TRUE(frameRateOverrides.empty());
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
 }
 
-TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_twoUids) {
-    RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120,
-                                 {.enableFrameRateOverride = Config::FrameRateOverride::Enabled});
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_twoUids) {
+    if (GetParam() == Config::FrameRateOverride::Disabled) {
+        return;
+    }
+
+    ASSERT_TRUE(GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ||
+                GetParam() == Config::FrameRateOverride::AppOverride ||
+                GetParam() == Config::FrameRateOverride::Enabled);
+
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
 
     std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f},
                                             {.ownerUid = 5678, .weight = 1.f}};
@@ -2392,9 +2585,16 @@
     EXPECT_TRUE(frameRateOverrides.empty());
 }
 
-TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_touch) {
-    RefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120,
-                                 {.enableFrameRateOverride = Config::FrameRateOverride::Enabled});
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_touch) {
+    if (GetParam() == Config::FrameRateOverride::Disabled) {
+        return;
+    }
+
+    ASSERT_TRUE(GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ||
+                GetParam() == Config::FrameRateOverride::AppOverride ||
+                GetParam() == Config::FrameRateOverride::Enabled);
+
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
 
     std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f}};
     layers[0].name = "Test layer";
@@ -2432,88 +2632,87 @@
     EXPECT_TRUE(frameRateOverrides.empty());
 }
 
-TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_DivisorIsNotDisplayRefreshRate_Enabled) {
-    RefreshRateSelector selector(kModes_60_120, kModeId120,
-                                 {.enableFrameRateOverride = Config::FrameRateOverride::Enabled});
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_DivisorIsNotDisplayRefreshRate) {
+    if (GetParam() == Config::FrameRateOverride::Disabled) {
+        return;
+    }
+
+    ASSERT_TRUE(GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ||
+                GetParam() == Config::FrameRateOverride::AppOverride ||
+                GetParam() == Config::FrameRateOverride::Enabled);
+
+    auto selector = createSelector(kModes_60_120, kModeId120);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     layers[0].name = "Test layer";
     layers[0].ownerUid = 1234;
     layers[0].desiredRefreshRate = 30_Hz;
-    layers[0].vote = LayerVoteType::ExplicitDefault;
 
+    const auto expetedFps =
+            GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ? 60_Hz : 30_Hz;
+    layers[0].vote = LayerVoteType::ExplicitDefault;
     auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
     EXPECT_EQ(1u, frameRateOverrides.size());
     ASSERT_EQ(1u, frameRateOverrides.count(1234));
-    EXPECT_EQ(30_Hz, frameRateOverrides.at(1234));
+    EXPECT_EQ(expetedFps, frameRateOverrides.at(1234));
 
     layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
     frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
     EXPECT_EQ(1u, frameRateOverrides.size());
     ASSERT_EQ(1u, frameRateOverrides.count(1234));
-    EXPECT_EQ(30_Hz, frameRateOverrides.at(1234));
+    EXPECT_EQ(expetedFps, frameRateOverrides.at(1234));
 
-    layers[0].vote = LayerVoteType::NoVote;
-    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_TRUE(frameRateOverrides.empty());
-
-    layers[0].vote = LayerVoteType::Min;
-    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_TRUE(frameRateOverrides.empty());
-
-    layers[0].vote = LayerVoteType::Max;
-    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_TRUE(frameRateOverrides.empty());
-
-    layers[0].vote = LayerVoteType::Heuristic;
-    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_TRUE(frameRateOverrides.empty());
-}
-
-TEST_F(RefreshRateSelectorTest,
-       getFrameRateOverrides_DivisorIsNotDisplayRefreshRate_EnabledForNativeRefreshRates) {
-    RefreshRateSelector selector(kModes_60_120, kModeId120,
-                                 {.enableFrameRateOverride =
-                                          Config::FrameRateOverride::EnabledForNativeRefreshRates});
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    layers[0].name = "Test layer";
-    layers[0].ownerUid = 1234;
-    layers[0].desiredRefreshRate = 30_Hz;
-    layers[0].vote = LayerVoteType::ExplicitDefault;
-
-    auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_EQ(1u, frameRateOverrides.size());
-    ASSERT_EQ(1u, frameRateOverrides.count(1234));
-    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
-    layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+    layers[0].vote = LayerVoteType::ExplicitExact;
     frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
     EXPECT_EQ(1u, frameRateOverrides.size());
     ASSERT_EQ(1u, frameRateOverrides.count(1234));
-    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
-    layers[0].vote = LayerVoteType::NoVote;
-    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_TRUE(frameRateOverrides.empty());
-
-    layers[0].vote = LayerVoteType::Min;
-    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_TRUE(frameRateOverrides.empty());
-
-    layers[0].vote = LayerVoteType::Max;
-    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_TRUE(frameRateOverrides.empty());
-
-    layers[0].vote = LayerVoteType::Heuristic;
-    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_TRUE(frameRateOverrides.empty());
+    EXPECT_EQ(expetedFps, frameRateOverrides.at(1234));
 }
 
-TEST_F(RefreshRateSelectorTest, getFrameRateOverrides_InPolicy) {
-    TestableRefreshRateSelector selector(kModes_30_60_72_90_120, kModeId120,
-                                         {.enableFrameRateOverride =
-                                                  Config::FrameRateOverride::Enabled});
+TEST_P(RefreshRateSelectorTest, renderFrameRateInvalidPolicy) {
+    auto selector = createSelector(kModes_60_120, kModeId120);
+
+    // The render frame rate cannot be greater than the physical refresh rate
+    {
+        const FpsRange physical = {60_Hz, 60_Hz};
+        const FpsRange render = {60_Hz, 120_Hz};
+        EXPECT_EQ(SetPolicyResult::Invalid,
+                  selector.setDisplayManagerPolicy(
+                          {kModeId60, {physical, render}, {physical, render}}));
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, renderFrameRateRestrictsPhysicalRefreshRate) {
+    auto selector = createSelector(kModes_60_120, kModeId120);
+
+    {
+        const FpsRange physical = {0_Hz, 120_Hz};
+        const FpsRange render = {0_Hz, 60_Hz};
+        EXPECT_EQ(SetPolicyResult::Changed,
+                  selector.setDisplayManagerPolicy(
+                          {kModeId60, {physical, render}, {physical, render}}));
+        const auto expectedMaxMode =
+                GetParam() == Config::FrameRateOverride::Enabled ? kMode120 : kMode60;
+        EXPECT_EQ(expectedMaxMode, selector.getMaxRefreshRateByPolicy());
+        EXPECT_EQ(kMode60, selector.getMinRefreshRateByPolicy());
+    }
+
+    {
+        const FpsRange physical = {0_Hz, 120_Hz};
+        const FpsRange render = {120_Hz, 120_Hz};
+        EXPECT_EQ(SetPolicyResult::Changed,
+                  selector.setDisplayManagerPolicy(
+                          {kModeId60, {physical, render}, {physical, render}}));
+        EXPECT_EQ(kMode120, selector.getMaxRefreshRateByPolicy());
+        EXPECT_EQ(kMode120, selector.getMinRefreshRateByPolicy());
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_InPolicy) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
+        return;
+    }
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     {
@@ -2566,5 +2765,153 @@
     EXPECT_EQ(30_Hz, frameRateOverrides.at(1234));
 }
 
+TEST_P(RefreshRateSelectorTest, renderFrameRates) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+    // [renderRate, refreshRate]
+    const auto expected = []() -> std::vector<std::pair<Fps, Fps>> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{30_Hz, 30_Hz},
+                        {60_Hz, 60_Hz},
+                        {72_Hz, 72_Hz},
+                        {90_Hz, 90_Hz},
+                        {120_Hz, 120_Hz}};
+            case Config::FrameRateOverride::Enabled:
+                return {{30_Hz, 30_Hz}, {36_Hz, 72_Hz}, {40_Hz, 120_Hz}, {45_Hz, 90_Hz},
+                        {60_Hz, 60_Hz}, {72_Hz, 72_Hz}, {90_Hz, 90_Hz},  {120_Hz, 120_Hz}};
+        }
+    }();
+
+    const auto& primaryRefreshRates = selector.getPrimaryFrameRates();
+    ASSERT_EQ(expected.size(), primaryRefreshRates.size());
+
+    for (size_t i = 0; i < expected.size(); i++) {
+        const auto [expectedRenderRate, expectedRefreshRate] = expected[i];
+        EXPECT_EQ(expectedRenderRate, primaryRefreshRates[i].fps);
+        EXPECT_EQ(expectedRefreshRate, primaryRefreshRates[i].modePtr->getFps());
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, refreshRateIsCappedWithRenderFrameRate) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
+        return;
+    }
+
+    auto selector = createSelector(kModes_60_120, kModeId60);
+
+    constexpr FpsRange k0_120Hz = {0_Hz, 120_Hz};
+    constexpr FpsRange k0_60Hz = {0_Hz, 60_Hz};
+
+    constexpr FpsRanges kAppRequest = {/*physical*/ k0_120Hz,
+                                       /*render*/ k0_120Hz};
+
+    EXPECT_SCORED_FRAME_RATE(kMode120, 120_Hz, selector.getBestScoredFrameRate());
+    {
+        constexpr FpsRanges kPrimary = {/*physical*/ k0_120Hz,
+                                        /*render*/ k0_120Hz};
+        EXPECT_EQ(SetPolicyResult::Changed,
+                  selector.setDisplayManagerPolicy({/*defaultMode*/ kModeId60,
+                                                    /*primaryRanges*/
+                                                    kPrimary,
+                                                    /*appRequestRanges*/
+                                                    kAppRequest}));
+    }
+    EXPECT_SCORED_FRAME_RATE(kMode120, 120_Hz, selector.getBestScoredFrameRate());
+
+    {
+        constexpr FpsRanges kPrimary = {/*physical*/ k0_60Hz,
+                                        /*render*/ k0_60Hz};
+        EXPECT_EQ(SetPolicyResult::Changed,
+                  selector.setDisplayManagerPolicy({/*defaultMode*/ kModeId60,
+                                                    /*primaryRanges*/
+                                                    kPrimary,
+                                                    /*appRequestRanges*/
+                                                    kAppRequest}));
+    }
+    EXPECT_SCORED_FRAME_RATE(kMode60, 60_Hz, selector.getBestScoredFrameRate());
+
+    {
+        constexpr FpsRanges kPrimary = {/*physical*/ k0_120Hz,
+                                        /*render*/ k0_60Hz};
+        EXPECT_EQ(SetPolicyResult::Changed,
+                  selector.setDisplayManagerPolicy({/*defaultMode*/ kModeId60,
+                                                    /*primaryRanges*/
+                                                    kPrimary,
+                                                    /*appRequestRanges*/
+                                                    kAppRequest}));
+    }
+    EXPECT_SCORED_FRAME_RATE(kMode60, 60_Hz, selector.getBestScoredFrameRate());
+}
+
+TEST_P(RefreshRateSelectorTest, renderFrameRates_60_120) {
+    auto selector = createSelector(kModes_60_120, kModeId120);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& layer = layers[0];
+
+    const auto expectedRenderRate =
+            GetParam() == Config::FrameRateOverride::Enabled ? 30_Hz : 60_Hz;
+
+    layer.name = "30Hz ExplicitDefault";
+    layer.desiredRefreshRate = 30_Hz;
+    layer.vote = LayerVoteType::ExplicitDefault;
+    EXPECT_SCORED_FRAME_RATE(kMode60, expectedRenderRate, selector.getBestScoredFrameRate(layers));
+
+    layer.name = "30Hz Heuristic";
+    layer.desiredRefreshRate = 30_Hz;
+    layer.vote = LayerVoteType::Heuristic;
+    EXPECT_SCORED_FRAME_RATE(kMode60, expectedRenderRate, selector.getBestScoredFrameRate(layers));
+
+    layer.name = "30Hz ExplicitExactOrMultiple";
+    layer.desiredRefreshRate = 30_Hz;
+    layer.vote = LayerVoteType::ExplicitExactOrMultiple;
+    EXPECT_SCORED_FRAME_RATE(kMode60, expectedRenderRate, selector.getBestScoredFrameRate(layers));
+}
+
+TEST_P(RefreshRateSelectorTest, idleWhenLowestRefreshRateIsNotDivisor) {
+    auto selector = createSelector(kModes_35_60_90, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].name = "Test layer";
+
+    const auto getIdleDisplayModeId = [&](LayerVoteType voteType,
+                                          bool touchActive) -> DisplayModeId {
+        layers[0].vote = voteType;
+        layers[0].desiredRefreshRate = 90_Hz;
+
+        const auto [ranking, signals] =
+                selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true});
+
+        // Refresh rate will be chosen by either touch state or idle state.
+        EXPECT_EQ(!touchActive, signals.idle);
+        return ranking.front().frameRateMode.modePtr->getId();
+    };
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId60, {0_Hz, 90_Hz}}));
+
+    // With no layers, idle should still be lower priority than touch boost.
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode({}, {.touch = true, .idle = true})->getId());
+
+    // Idle should be higher precedence than other layer frame rate considerations.
+    selector.setActiveModeId(kModeId90);
+    {
+        constexpr bool kTouchActive = false;
+        EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::NoVote, kTouchActive));
+        EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::Min, kTouchActive));
+        EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::Max, kTouchActive));
+        EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::Heuristic, kTouchActive));
+        EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::ExplicitDefault, kTouchActive));
+        EXPECT_EQ(kModeId35,
+                  getIdleDisplayModeId(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
+    }
+
+    // Idle should be applied rather than the active mode when there are no layers.
+    EXPECT_EQ(kModeId35, selector.getBestFrameRateMode({}, {.idle = true})->getId());
+}
+
 } // namespace
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
index 6ffc039..1dd4f25 100644
--- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
@@ -1320,6 +1320,7 @@
     insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 3, 3000000, {}, GameMode::Performance);
     insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 4, 4000000, {}, GameMode::Battery);
     insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 5, 4000000, {}, GameMode::Battery);
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 6, 5000000, {}, GameMode::Custom);
 
     std::string pulledData;
     EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData));
@@ -1327,9 +1328,9 @@
     SurfaceflingerStatsLayerInfoWrapper atomList;
     ASSERT_TRUE(atomList.ParseFromString(pulledData));
     // The first time record is never uploaded to stats.
-    ASSERT_EQ(atomList.atom_size(), 3);
+    ASSERT_EQ(atomList.atom_size(), 4);
     // Layers are ordered based on the hash in LayerStatsKey. For this test, the order happens to
-    // be: 0 - Battery 1 - Performance 2 - Standard
+    // be: 0 - Battery 1 - Custom 2 - Performance 3 - Standard
     const SurfaceflingerStatsLayerInfo& atom0 = atomList.atom(0);
 
     EXPECT_EQ(atom0.layer_name(), genLayerName(LAYER_ID_0));
@@ -1364,7 +1365,7 @@
     EXPECT_EQ(atom1.uid(), UID_0);
     EXPECT_EQ(atom1.display_refresh_rate_bucket(), REFRESH_RATE_BUCKET_0);
     EXPECT_EQ(atom1.render_rate_bucket(), RENDER_RATE_BUCKET_0);
-    EXPECT_EQ(atom1.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_PERFORMANCE);
+    EXPECT_EQ(atom1.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_CUSTOM);
 
     const SurfaceflingerStatsLayerInfo& atom2 = atomList.atom(2);
 
@@ -1377,12 +1378,30 @@
     EXPECT_THAT(atom2.latch_to_present(), HistogramEq(buildExpectedHistogram({2}, {1})));
     EXPECT_THAT(atom2.desired_to_present(), HistogramEq(buildExpectedHistogram({1}, {1})));
     EXPECT_THAT(atom2.post_to_acquire(), HistogramEq(buildExpectedHistogram({1}, {1})));
-    EXPECT_EQ(atom2.late_acquire_frames(), LATE_ACQUIRE_FRAMES);
-    EXPECT_EQ(atom2.bad_desired_present_frames(), BAD_DESIRED_PRESENT_FRAMES);
+    EXPECT_EQ(atom2.late_acquire_frames(), 0);
+    EXPECT_EQ(atom2.bad_desired_present_frames(), 0);
     EXPECT_EQ(atom2.uid(), UID_0);
     EXPECT_EQ(atom2.display_refresh_rate_bucket(), REFRESH_RATE_BUCKET_0);
     EXPECT_EQ(atom2.render_rate_bucket(), RENDER_RATE_BUCKET_0);
-    EXPECT_EQ(atom2.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_STANDARD);
+    EXPECT_EQ(atom2.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_PERFORMANCE);
+
+    const SurfaceflingerStatsLayerInfo& atom3 = atomList.atom(3);
+
+    EXPECT_EQ(atom3.layer_name(), genLayerName(LAYER_ID_0));
+    EXPECT_EQ(atom3.total_frames(), 1);
+    EXPECT_EQ(atom3.dropped_frames(), 0);
+    EXPECT_THAT(atom3.present_to_present(), HistogramEq(buildExpectedHistogram({1}, {1})));
+    EXPECT_THAT(atom3.post_to_present(), HistogramEq(buildExpectedHistogram({4}, {1})));
+    EXPECT_THAT(atom3.acquire_to_present(), HistogramEq(buildExpectedHistogram({3}, {1})));
+    EXPECT_THAT(atom3.latch_to_present(), HistogramEq(buildExpectedHistogram({2}, {1})));
+    EXPECT_THAT(atom3.desired_to_present(), HistogramEq(buildExpectedHistogram({1}, {1})));
+    EXPECT_THAT(atom3.post_to_acquire(), HistogramEq(buildExpectedHistogram({1}, {1})));
+    EXPECT_EQ(atom3.late_acquire_frames(), LATE_ACQUIRE_FRAMES);
+    EXPECT_EQ(atom3.bad_desired_present_frames(), BAD_DESIRED_PRESENT_FRAMES);
+    EXPECT_EQ(atom3.uid(), UID_0);
+    EXPECT_EQ(atom3.display_refresh_rate_bucket(), REFRESH_RATE_BUCKET_0);
+    EXPECT_EQ(atom3.render_rate_bucket(), RENDER_RATE_BUCKET_0);
+    EXPECT_EQ(atom3.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_STANDARD);
 }
 
 TEST_F(TimeStatsTest, layerStatsCallback_pullsMultipleLayers) {
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index 488d4a9..d84698f 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -42,6 +42,8 @@
 using testing::Return;
 
 using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+using frontend::TransactionHandler;
+
 constexpr nsecs_t TRANSACTION_TIMEOUT = s2ns(5);
 class TransactionApplicationTest : public testing::Test {
 public: