Merge "Dispatch cancel event to the correct target display" into main
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc
index fc0801c..3e6d2e0 100644
--- a/cmds/atrace/atrace.rc
+++ b/cmds/atrace/atrace.rc
@@ -93,6 +93,10 @@
     chmod 0666 /sys/kernel/tracing/events/binder/binder_unlock/enable
     chmod 0666 /sys/kernel/debug/tracing/events/binder/binder_set_priority/enable
     chmod 0666 /sys/kernel/tracing/events/binder/binder_set_priority/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/binder/binder_command/enable
+    chmod 0666 /sys/kernel/tracing/events/binder/binder_command/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/binder/binder_return/enable
+    chmod 0666 /sys/kernel/tracing/events/binder/binder_return/enable
     chmod 0666 /sys/kernel/debug/tracing/events/i2c/enable
     chmod 0666 /sys/kernel/tracing/events/i2c/enable
     chmod 0666 /sys/kernel/debug/tracing/events/i2c/i2c_read/enable
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index 1123d4f..073d0c4 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -757,7 +757,7 @@
 binder::Status InstalldNativeService::createAppDataLocked(
         const std::optional<std::string>& uuid, const std::string& packageName, int32_t userId,
         int32_t flags, int32_t appId, int32_t previousAppId, const std::string& seInfo,
-        int32_t targetSdkVersion, int64_t* _aidl_return) {
+        int32_t targetSdkVersion, int64_t* ceDataInode, int64_t* deDataInode) {
     ENFORCE_UID(AID_SYSTEM);
     ENFORCE_VALID_USER(userId);
     CHECK_ARGUMENT_UUID(uuid);
@@ -767,7 +767,8 @@
     const char* pkgname = packageName.c_str();
 
     // Assume invalid inode unless filled in below
-    if (_aidl_return != nullptr) *_aidl_return = -1;
+    if (ceDataInode != nullptr) *ceDataInode = -1;
+    if (deDataInode != nullptr) *deDataInode = -1;
 
     int32_t uid = multiuser_get_uid(userId, appId);
 
@@ -805,12 +806,12 @@
 
         // And return the CE inode of the top-level data directory so we can
         // clear contents while CE storage is locked
-        if (_aidl_return != nullptr) {
+        if (ceDataInode != nullptr) {
             ino_t result;
             if (get_path_inode(path, &result) != 0) {
                 return error("Failed to get_path_inode for " + path);
             }
-            *_aidl_return = static_cast<uint64_t>(result);
+            *ceDataInode = static_cast<uint64_t>(result);
         }
     }
     if (flags & FLAG_STORAGE_DE) {
@@ -829,6 +830,14 @@
         if (!prepare_app_profile_dir(packageName, appId, userId)) {
             return error("Failed to prepare profiles for " + packageName);
         }
+
+        if (deDataInode != nullptr) {
+            ino_t result;
+            if (get_path_inode(path, &result) != 0) {
+                return error("Failed to get_path_inode for " + path);
+            }
+            *deDataInode = static_cast<uint64_t>(result);
+        }
     }
 
     if (flags & FLAG_STORAGE_SDK) {
@@ -892,14 +901,14 @@
 binder::Status InstalldNativeService::createAppData(
         const std::optional<std::string>& uuid, const std::string& packageName, int32_t userId,
         int32_t flags, int32_t appId, int32_t previousAppId, const std::string& seInfo,
-        int32_t targetSdkVersion, int64_t* _aidl_return) {
+        int32_t targetSdkVersion, int64_t* ceDataInode, int64_t* deDataInode) {
     ENFORCE_UID(AID_SYSTEM);
     ENFORCE_VALID_USER(userId);
     CHECK_ARGUMENT_UUID(uuid);
     CHECK_ARGUMENT_PACKAGE_NAME(packageName);
     LOCK_PACKAGE_USER();
     return createAppDataLocked(uuid, packageName, userId, flags, appId, previousAppId, seInfo,
-                               targetSdkVersion, _aidl_return);
+                               targetSdkVersion, ceDataInode, deDataInode);
 }
 
 binder::Status InstalldNativeService::createAppData(
@@ -910,9 +919,12 @@
     // Locking is performed depeer in the callstack.
 
     int64_t ceDataInode = -1;
+    int64_t deDataInode = -1;
     auto status = createAppData(args.uuid, args.packageName, args.userId, args.flags, args.appId,
-            args.previousAppId, args.seInfo, args.targetSdkVersion, &ceDataInode);
+                                args.previousAppId, args.seInfo, args.targetSdkVersion,
+                                &ceDataInode, &deDataInode);
     _aidl_return->ceDataInode = ceDataInode;
+    _aidl_return->deDataInode = deDataInode;
     _aidl_return->exceptionCode = status.exceptionCode();
     _aidl_return->exceptionMessage = status.exceptionMessage();
     return ok();
@@ -1839,7 +1851,8 @@
         }
 
         if (!createAppDataLocked(toUuid, packageName, userId, FLAG_STORAGE_CE | FLAG_STORAGE_DE,
-                                 appId, /* previousAppId */ -1, seInfo, targetSdkVersion, nullptr)
+                                 appId, /* previousAppId */ -1, seInfo, targetSdkVersion, nullptr,
+                                 nullptr)
                      .isOk()) {
             res = error("Failed to create package target");
             goto fail;
diff --git a/cmds/installd/InstalldNativeService.h b/cmds/installd/InstalldNativeService.h
index 0f28234..1ec092d 100644
--- a/cmds/installd/InstalldNativeService.h
+++ b/cmds/installd/InstalldNativeService.h
@@ -68,7 +68,8 @@
     binder::Status createAppData(const std::optional<std::string>& uuid,
                                  const std::string& packageName, int32_t userId, int32_t flags,
                                  int32_t appId, int32_t previousAppId, const std::string& seInfo,
-                                 int32_t targetSdkVersion, int64_t* _aidl_return);
+                                 int32_t targetSdkVersion, int64_t* ceDataInode,
+                                 int64_t* deDataInode);
 
     binder::Status createAppData(
             const android::os::CreateAppDataArgs& args,
@@ -238,7 +239,7 @@
                                        const std::string& packageName, int32_t userId,
                                        int32_t flags, int32_t appId, int32_t previousAppId,
                                        const std::string& seInfo, int32_t targetSdkVersion,
-                                       int64_t* _aidl_return);
+                                       int64_t* ceDataInode, int64_t* deDataInode);
     binder::Status restoreconAppDataLocked(const std::optional<std::string>& uuid,
                                            const std::string& packageName, int32_t userId,
                                            int32_t flags, int32_t appId, const std::string& seInfo);
diff --git a/cmds/installd/binder/android/os/CreateAppDataResult.aidl b/cmds/installd/binder/android/os/CreateAppDataResult.aidl
index 3b8fa6b..463489e 100644
--- a/cmds/installd/binder/android/os/CreateAppDataResult.aidl
+++ b/cmds/installd/binder/android/os/CreateAppDataResult.aidl
@@ -19,6 +19,7 @@
 /** {@hide} */
 parcelable CreateAppDataResult {
     long ceDataInode;
+    long deDataInode;
     int exceptionCode;
     @utf8InCpp String exceptionMessage;
 }
diff --git a/cmds/installd/otapreopt.cpp b/cmds/installd/otapreopt.cpp
index a447cda..822ab7f 100644
--- a/cmds/installd/otapreopt.cpp
+++ b/cmds/installd/otapreopt.cpp
@@ -437,6 +437,9 @@
                     maybe_open_reference_profile(parameters_.pkgName, parameters_.apk_path,
                                                  parameters_.profile_name, profile_guided,
                                                  is_public, parameters_.uid, is_secondary_dex);
+            // `maybe_open_reference_profile` installs a hook that clears the profile on
+            // destruction. Disable it.
+            reference_profile.DisableCleanup();
             struct stat sbuf;
             if (reference_profile.fd() == -1 ||
                 (fstat(reference_profile.fd(), &sbuf) != -1 && sbuf.st_size == 0)) {
diff --git a/cmds/installd/tests/installd_dexopt_test.cpp b/cmds/installd/tests/installd_dexopt_test.cpp
index c4071c6..ee91d80 100644
--- a/cmds/installd/tests/installd_dexopt_test.cpp
+++ b/cmds/installd/tests/installd_dexopt_test.cpp
@@ -197,6 +197,7 @@
     std::string app_oat_dir_;
 
     int64_t ce_data_inode_;
+    int64_t de_data_inode_;
 
     std::string secondary_dex_ce_;
     std::string secondary_dex_ce_link_;
@@ -261,16 +262,10 @@
         }
 
         // Create the app user data.
-        binder::Status status = service_->createAppData(
-                volume_uuid_,
-                package_name_,
-                kTestUserId,
-                kAppDataFlags,
-                kTestAppUid,
-                0 /* previousAppId */,
-                se_info_,
-                kOSdkVersion,
-                &ce_data_inode_);
+        binder::Status status =
+                service_->createAppData(volume_uuid_, package_name_, kTestUserId, kAppDataFlags,
+                                        kTestAppUid, 0 /* previousAppId */, se_info_, kOSdkVersion,
+                                        &ce_data_inode_, &de_data_inode_);
         if (!status.isOk()) {
             return ::testing::AssertionFailure() << "Could not create app data: "
                                                  << status.toString8().c_str();
@@ -1350,16 +1345,10 @@
     ASSERT_EQ(0, chmod(ref_profile_dir.c_str(), 0700));
 
     // Run createAppData again which will offer to fix-up the profile directories.
-    ASSERT_BINDER_SUCCESS(service_->createAppData(
-            volume_uuid_,
-            package_name_,
-            kTestUserId,
-            kAppDataFlags,
-            kTestAppUid,
-            0 /* previousAppId */,
-            se_info_,
-            kOSdkVersion,
-            &ce_data_inode_));
+    ASSERT_BINDER_SUCCESS(service_->createAppData(volume_uuid_, package_name_, kTestUserId,
+                                                  kAppDataFlags, kTestAppUid, 0 /* previousAppId */,
+                                                  se_info_, kOSdkVersion, &ce_data_inode_,
+                                                  &de_data_inode_));
 
     // Check the file access.
     CheckFileAccess(cur_profile_dir, kTestAppUid, kTestAppUid, 0700 | S_IFDIR);
@@ -1492,18 +1481,13 @@
     void createAppProfilesForBootMerge(size_t number_of_profiles) {
         for (size_t i = 0; i < number_of_profiles; i++) {
             int64_t ce_data_inode;
+            int64_t de_data_inode;
             std::string package_name = "dummy_test_pkg" + std::to_string(i);
             LOG(INFO) << package_name;
-            ASSERT_BINDER_SUCCESS(service_->createAppData(
-                    volume_uuid_,
-                    package_name,
-                    kTestUserId,
-                    kAppDataFlags,
-                    kTestAppUid,
-                    0 /* previousAppId */,
-                    se_info_,
-                    kOSdkVersion,
-                    &ce_data_inode));
+            ASSERT_BINDER_SUCCESS(
+                    service_->createAppData(volume_uuid_, package_name, kTestUserId, kAppDataFlags,
+                                            kTestAppUid, 0 /* previousAppId */, se_info_,
+                                            kOSdkVersion, &ce_data_inode, &de_data_inode));
             extra_apps_.push_back(package_name);
             extra_ce_data_inodes_.push_back(ce_data_inode);
             std::string profile = create_current_profile_path(
diff --git a/include/android/bitmap.h b/include/android/bitmap.h
index 35f87f9..87a14c0 100644
--- a/include/android/bitmap.h
+++ b/include/android/bitmap.h
@@ -196,7 +196,7 @@
  *
  *  @param userContext Pointer to user-defined data passed to
  *         {@link AndroidBitmap_compress}.
- *  @param data Compressed data of |size| bytes to write.
+ *  @param data Compressed data of `size` bytes to write.
  *  @param size Length in bytes of data to write.
  *  @return Whether the operation succeeded.
  */
@@ -205,7 +205,7 @@
                                                 size_t size) __INTRODUCED_IN(30);
 
 /**
- *  Compress |pixels| as described by |info|.
+ *  Compress `pixels` as described by `info`.
  *
  *  Available since API level 30.
  *
diff --git a/include/android/sharedmem.h b/include/android/sharedmem.h
index e0a8045..645fa8a 100644
--- a/include/android/sharedmem.h
+++ b/include/android/sharedmem.h
@@ -53,7 +53,7 @@
 /**
  * Create a shared memory region.
  *
- * Create shared memory region and returns an file descriptor.  The resulting file descriptor can be
+ * Create shared memory region and returns a file descriptor.  The resulting file descriptor can be
  * mmap'ed to process memory space with PROT_READ | PROT_WRITE | PROT_EXEC. Access to shared memory
  * region can be restricted with {@link ASharedMemory_setProt}.
  *
@@ -65,7 +65,7 @@
  * cmsg(3) man pages for more information.
  *
  * If you intend to share this file descriptor with a child process after
- * calling exec(3), note that you will need to use fcntl(2) with FD_SETFD
+ * calling exec(3), note that you will need to use fcntl(2) with F_SETFD
  * to clear the FD_CLOEXEC flag for this to work on all versions of Android.
  *
  * Available since API level 26.
diff --git a/include/android/thermal.h b/include/android/thermal.h
index 32580ba..1f477f8 100644
--- a/include/android/thermal.h
+++ b/include/android/thermal.h
@@ -188,13 +188,13 @@
  * Note that this only attempts to track the headroom of slow-moving sensors, such as
  * the skin temperature sensor. This means that there is no benefit to calling this function
  * more frequently than about once per second, and attempted to call significantly
- * more frequently may result in the function returning {@code NaN}.
+ * more frequently may result in the function returning `NaN`.
  *
  * In addition, in order to be able to provide an accurate forecast, the system does
  * not attempt to forecast until it has multiple temperature samples from which to
  * extrapolate. This should only take a few seconds from the time of the first call,
  * but during this time, no forecasting will occur, and the current headroom will be
- * returned regardless of the value of {@code forecastSeconds}.
+ * returned regardless of the value of `forecastSeconds`.
  *
  * The value returned is a non-negative float that represents how much of the thermal envelope
  * is in use (or is forecasted to be in use). A value of 1.0 indicates that the device is
diff --git a/include/ftl/OWNERS b/include/ftl/OWNERS
new file mode 100644
index 0000000..3f61292
--- /dev/null
+++ b/include/ftl/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/native:/services/surfaceflinger/OWNERS
\ No newline at end of file
diff --git a/include/ftl/enum.h b/include/ftl/enum.h
index 075d12b..2c86e2e 100644
--- a/include/ftl/enum.h
+++ b/include/ftl/enum.h
@@ -25,12 +25,12 @@
 
 #include <ftl/string.h>
 
-// Returns the name of enumerator E::V (i.e. "V") as std::optional<std::string_view> by parsing the
-// compiler-generated string literal for the signature of this function. The function is defined in
-// the global namespace with a short name and inferred return type to reduce bloat in the read-only
-// data segment.
-template <typename E, E V>
-constexpr auto ftl_enum() {
+// Returns the name of enumerator E::V and optionally the class (i.e. "E::V" or "V") as
+// std::optional<std::string_view> by parsing the compiler-generated string literal for the
+// signature of this function. The function is defined in the global namespace with a short name
+// and inferred return type to reduce bloat in the read-only data segment.
+template <bool S, typename E, E V>
+constexpr auto ftl_enum_builder() {
   static_assert(std::is_enum_v<E>);
 
   using R = std::optional<std::string_view>;
@@ -58,7 +58,9 @@
   //   V = android::test::Enum::kValue
   //
   view = view.substr(value_begin);
-  const auto name_begin = view.rfind("::"sv);
+  const auto pos = S ? view.rfind("::"sv) - 2 : view.npos;
+
+  const auto name_begin = view.rfind("::"sv, pos);
   if (name_begin == view.npos) return R{};
 
   // Chop off the leading "::".
@@ -68,6 +70,18 @@
   return name.find(')') == view.npos ? R{name} : R{};
 }
 
+// Returns the name of enumerator E::V (i.e. "V") as std::optional<std::string_view>
+template <typename E, E V>
+constexpr auto ftl_enum() {
+  return ftl_enum_builder<false, E, V>();
+}
+
+// Returns the name of enumerator and class E::V (i.e. "E::V") as std::optional<std::string_view>
+template <typename E, E V>
+constexpr auto ftl_enum_full() {
+  return ftl_enum_builder<true, E, V>();
+}
+
 namespace android::ftl {
 
 // Trait for determining whether a type is specifically a scoped enum or not. By definition, a
@@ -191,6 +205,11 @@
   static constexpr auto value = ftl_enum<decltype(V), V>();
 };
 
+template <auto V>
+struct EnumNameFull {
+  static constexpr auto value = ftl_enum_full<decltype(V), V>();
+};
+
 template <auto I>
 struct FlagName {
   using E = decltype(I);
@@ -230,6 +249,18 @@
   return *kName;
 }
 
+// Returns a stringified enumerator with class at compile time.
+//
+//   enum class E { A, B, C };
+//   static_assert(ftl::enum_name<E::B>() == "E::B");
+//
+template <auto V>
+constexpr std::string_view enum_name_full() {
+  constexpr auto kName = ftl_enum_full<decltype(V), V>();
+  static_assert(kName, "Unknown enumerator");
+  return *kName;
+}
+
 // Returns a stringified enumerator, possibly at compile time.
 //
 //   enum class E { A, B, C, F = 5, ftl_last = F };
@@ -249,6 +280,25 @@
   return kRange.values[value - kBegin];
 }
 
+// Returns a stringified enumerator with class, possibly at compile time.
+//
+//   enum class E { A, B, C, F = 5, ftl_last = F };
+//
+//   static_assert(ftl::enum_name(E::C).value_or("?") == "E::C");
+//   static_assert(ftl::enum_name(E{3}).value_or("?") == "?");
+//
+template <typename E>
+constexpr std::optional<std::string_view> enum_name_full(E v) {
+  const auto value = to_underlying(v);
+
+  constexpr auto kBegin = to_underlying(enum_begin_v<E>);
+  constexpr auto kLast = to_underlying(enum_last_v<E>);
+  if (value < kBegin || value > kLast) return {};
+
+  constexpr auto kRange = details::EnumRange<E, details::EnumNameFull>{};
+  return kRange.values[value - kBegin];
+}
+
 // Returns a stringified flag enumerator, possibly at compile time.
 //
 //   enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
@@ -282,6 +332,21 @@
   return to_string(to_underlying(v));
 }
 
+// Returns a stringified enumerator with class, or its integral value if not named.
+//
+//   enum class E { A, B, C, F = 5, ftl_last = F };
+//
+//   assert(ftl::enum_string(E::C) == "E::C");
+//   assert(ftl::enum_string(E{3}) == "3");
+//
+template <typename E>
+inline std::string enum_string_full(E v) {
+  if (const auto name = enum_name_full(v)) {
+      return std::string(*name);
+  }
+  return to_string(to_underlying(v));
+}
+
 // Returns a stringified flag enumerator, or its integral value if not named.
 //
 //   enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h
index 0e3fbb1..63c0e40 100644
--- a/include/input/PrintTools.h
+++ b/include/input/PrintTools.h
@@ -75,11 +75,12 @@
 }
 
 /**
- * Convert a map to string. Both keys and values of the map should be integral type.
+ * Convert a map or multimap to string. Both keys and values of the map should be integral type.
  */
-template <typename K, typename V>
-std::string dumpMap(const std::map<K, V>& map, std::string (*keyToString)(const K&) = constToString,
-                    std::string (*valueToString)(const V&) = constToString) {
+template <typename T>
+std::string dumpMap(const T& map,
+                    std::string (*keyToString)(const typename T::key_type&) = constToString,
+                    std::string (*valueToString)(const typename T::mapped_type&) = constToString) {
     std::string out;
     for (const auto& [k, v] : map) {
         if (!out.empty()) {
@@ -104,15 +105,13 @@
     return out.empty() ? "{}" : (out + "}");
 }
 
-/**
- * Convert a vector to a string. The values of the vector should be of a type supported by
- * constToString.
- */
+/** Convert a vector to a string. */
 template <typename T>
-std::string dumpVector(std::vector<T> values) {
-    std::string dump = constToString(values[0]);
+std::string dumpVector(const std::vector<T>& values,
+                       std::string (*valueToString)(const T&) = constToString) {
+    std::string dump = valueToString(values[0]);
     for (size_t i = 1; i < values.size(); i++) {
-        dump += ", " + constToString(values[i]);
+        dump += ", " + valueToString(values[i]);
     }
     return dump;
 }
diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp
index 589df9a..ef0ae4f 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -54,6 +54,11 @@
 // Another arbitrary value a binder count needs to drop below before another callback will be called
 uint32_t BpBinder::sBinderProxyCountLowWatermark = 2000;
 
+std::atomic<uint32_t> BpBinder::sBinderProxyCount(0);
+std::atomic<uint32_t> BpBinder::sBinderProxyCountWarned(0);
+
+static constexpr uint32_t kBinderProxyCountWarnInterval = 5000;
+
 // Log any transactions for which the data exceeds this size
 #define LOG_TRANSACTIONS_OVER_SIZE (300 * 1024)
 
@@ -193,6 +198,18 @@
         }
         sTrackingMap[trackedUid]++;
     }
+    uint32_t numProxies = sBinderProxyCount.fetch_add(1, std::memory_order_relaxed);
+    uint32_t numLastWarned = sBinderProxyCountWarned.load(std::memory_order_relaxed);
+    uint32_t numNextWarn = numLastWarned + kBinderProxyCountWarnInterval;
+    if (numProxies >= numNextWarn) {
+        // Multiple threads can get here, make sure only one of them gets to
+        // update the warn counter.
+        if (sBinderProxyCountWarned.compare_exchange_strong(numLastWarned,
+                                                            numNextWarn,
+                                                            std::memory_order_relaxed)) {
+            ALOGW("Unexpectedly many live BinderProxies: %d\n", numProxies);
+        }
+    }
     return sp<BpBinder>::make(BinderHandle{handle}, trackedUid);
 }
 
@@ -604,6 +621,7 @@
             }
         }
     }
+    --sBinderProxyCount;
 
     if (ipc) {
         ipc->expungeHandle(binderHandle(), this);
@@ -688,6 +706,11 @@
     return 0;
 }
 
+uint32_t BpBinder::getBinderProxyCount()
+{
+    return sBinderProxyCount.load();
+}
+
 void BpBinder::getCountByUid(Vector<uint32_t>& uids, Vector<uint32_t>& counts)
 {
     AutoMutex _l(sTrackingLock);
diff --git a/libs/binder/include/binder/BpBinder.h b/libs/binder/include/binder/BpBinder.h
index 5496d61..a5c6094 100644
--- a/libs/binder/include/binder/BpBinder.h
+++ b/libs/binder/include/binder/BpBinder.h
@@ -87,6 +87,7 @@
     static void         setCountByUidEnabled(bool enable);
     static void         setLimitCallback(binder_proxy_limit_callback cb);
     static void         setBinderProxyCountWatermarks(int high, int low);
+    static uint32_t     getBinderProxyCount();
 
     std::optional<int32_t> getDebugBinderHandle() const;
 
@@ -208,6 +209,8 @@
     static uint32_t                             sBinderProxyCountLowWatermark;
     static bool                                 sBinderProxyThrottleCreate;
     static std::unordered_map<int32_t,uint32_t> sLastLimitCallbackMap;
+    static std::atomic<uint32_t>                sBinderProxyCount;
+    static std::atomic<uint32_t>                sBinderProxyCountWarned;
 };
 
 } // namespace android
diff --git a/libs/binder/ndk/include_ndk/android/binder_status.h b/libs/binder/ndk/include_ndk/android/binder_status.h
index 76c7aac..4786c89 100644
--- a/libs/binder/ndk/include_ndk/android/binder_status.h
+++ b/libs/binder/ndk/include_ndk/android/binder_status.h
@@ -25,6 +25,7 @@
 
 #pragma once
 
+#include <assert.h>
 #include <errno.h>
 #include <stdbool.h>
 #include <stdint.h>
diff --git a/libs/binder/ndk/tests/Android.bp b/libs/binder/ndk/tests/Android.bp
index 8ee396e..8fb755c 100644
--- a/libs/binder/ndk/tests/Android.bp
+++ b/libs/binder/ndk/tests/Android.bp
@@ -80,6 +80,28 @@
     require_root: true,
 }
 
+cc_test_host {
+    name: "libbinder_ndk_unit_test_host",
+    defaults: ["test_libbinder_ndk_defaults"],
+    srcs: ["libbinder_ndk_unit_test_host.cpp"],
+    test_suites: [
+        "general-tests",
+    ],
+    test_options: {
+        unit_test: true,
+    },
+    static_libs: [
+        "libbase",
+        "libbinder_ndk",
+        "libbinder",
+        "libcutils",
+        "libfakeservicemanager",
+        "libgmock",
+        "liblog",
+        "libutils",
+    ],
+}
+
 cc_test {
     name: "binderVendorDoubleLoadTest",
     vendor: true,
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index 25b8e97..15708ca 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -39,7 +39,6 @@
 #include <condition_variable>
 #include <iostream>
 #include <mutex>
-#include <optional>
 #include <thread>
 
 #include "android/binder_ibinder.h"
@@ -433,21 +432,6 @@
     EXPECT_EQ(STATUS_OK, AIBinder_ping(binder.get()));
 }
 
-TEST(NdkBinder, IsUpdatable) {
-    bool isUpdatable = AServiceManager_isUpdatableViaApex("android.hardware.light.ILights/default");
-    EXPECT_EQ(isUpdatable, false);
-}
-
-TEST(NdkBinder, GetUpdatableViaApex) {
-    std::optional<std::string> updatableViaApex;
-    AServiceManager_getUpdatableApexName(
-            "android.hardware.light.ILights/default", &updatableViaApex,
-            [](const char* apexName, void* context) {
-                *static_cast<std::optional<std::string>*>(context) = apexName;
-            });
-    EXPECT_EQ(updatableViaApex, std::nullopt) << *updatableViaApex;
-}
-
 // This is too slow
 TEST(NdkBinder, CheckLazyServiceShutDown) {
     ndk::SpAIBinder binder(AServiceManager_waitForService(kLazyBinderNdkUnitTestService));
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test_host.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test_host.cpp
new file mode 100644
index 0000000..0a3021d
--- /dev/null
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test_host.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 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 <android/binder_manager.h>
+#include <binder/IServiceManager.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <utils/String16.h>
+#include <utils/String8.h>
+#include <utils/StrongPointer.h>
+
+#include <optional>
+
+#include "fakeservicemanager/FakeServiceManager.h"
+
+using android::FakeServiceManager;
+using android::setDefaultServiceManager;
+using android::sp;
+using android::String16;
+using android::String8;
+using testing::_;
+using testing::Eq;
+using testing::Mock;
+using testing::NiceMock;
+using testing::Optional;
+using testing::Return;
+
+struct MockServiceManager : FakeServiceManager {
+    MOCK_METHOD1(updatableViaApex, std::optional<String16>(const String16&));
+};
+
+struct AServiceManager : testing::Test {
+    static sp<MockServiceManager> mockSM;
+
+    static void InitMock() {
+        mockSM = new NiceMock<MockServiceManager>;
+        setDefaultServiceManager(mockSM);
+    }
+
+    void TearDown() override { Mock::VerifyAndClear(mockSM.get()); }
+
+    void ExpectUpdatableViaApexReturns(std::optional<String16> apexName) {
+        EXPECT_CALL(*mockSM, updatableViaApex(_)).WillRepeatedly(Return(apexName));
+    }
+};
+
+sp<MockServiceManager> AServiceManager::mockSM;
+
+TEST_F(AServiceManager, isUpdatableViaApex) {
+    auto apexFoo = String16("com.android.hardware.foo");
+    ExpectUpdatableViaApexReturns(apexFoo);
+
+    bool isUpdatable = AServiceManager_isUpdatableViaApex("android.hardware.foo.IFoo/default");
+    EXPECT_EQ(isUpdatable, true);
+}
+
+TEST_F(AServiceManager, isUpdatableViaApex_Not) {
+    ExpectUpdatableViaApexReturns(std::nullopt);
+
+    bool isUpdatable = AServiceManager_isUpdatableViaApex("android.hardware.foo.IFoo/default");
+    EXPECT_EQ(isUpdatable, false);
+}
+
+void getUpdatableApexNameCallback(const char* apexName, void* context) {
+    *(static_cast<std::optional<std::string>*>(context)) = apexName;
+}
+
+TEST_F(AServiceManager, getUpdatableApexName) {
+    auto apexFoo = String16("com.android.hardware.foo");
+    ExpectUpdatableViaApexReturns(apexFoo);
+
+    std::optional<std::string> result;
+    AServiceManager_getUpdatableApexName("android.hardware.foo.IFoo/default", &result,
+                                         getUpdatableApexNameCallback);
+    EXPECT_THAT(result, Optional(std::string(String8(apexFoo))));
+}
+
+TEST_F(AServiceManager, getUpdatableApexName_Null) {
+    ExpectUpdatableViaApexReturns(std::nullopt);
+
+    std::optional<std::string> result;
+    AServiceManager_getUpdatableApexName("android.hardware.foo.IFoo/default", &result,
+                                         getUpdatableApexNameCallback);
+    EXPECT_THAT(result, Eq(std::nullopt));
+}
+
+int main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+    AServiceManager::InitMock();
+    return RUN_ALL_TESTS();
+}
diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs
index 1bbd674..896b78f 100644
--- a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs
+++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs
@@ -35,10 +35,26 @@
 
 /// This API automatically fuzzes provided service
 pub fn fuzz_service(binder: &mut SpIBinder, fuzzer_data: &[u8]) {
-    let ptr = binder.as_native_mut() as *mut c_void;
+    let mut binders = [binder];
+    fuzz_multiple_services(&mut binders, fuzzer_data);
+}
+
+/// This API automatically fuzzes provided services
+pub fn fuzz_multiple_services(binders: &mut [&mut SpIBinder], fuzzer_data: &[u8]) {
+    let mut cppBinders = vec![];
+    for binder in binders.iter_mut() {
+        let ptr = binder.as_native_mut() as *mut c_void;
+        cppBinders.push(ptr);
+    }
+
     unsafe {
-        // Safety: `SpIBinder::as_native_mut` and `slice::as_ptr` always
+        // Safety: `Vec::as_mut_ptr` and `slice::as_ptr` always
         // return valid pointers.
-        fuzzRustService(ptr, fuzzer_data.as_ptr(), fuzzer_data.len());
+        fuzzRustService(
+            cppBinders.as_mut_ptr(),
+            cppBinders.len(),
+            fuzzer_data.as_ptr(),
+            fuzzer_data.len(),
+        );
     }
 }
diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp
index 831bd56..cfdd2ab 100644
--- a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp
+++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp
@@ -21,5 +21,5 @@
     void createRandomParcel(void* aParcel, const uint8_t* data, size_t len);
 
     // This API is used by fuzzers to automatically fuzz aidl services
-    void fuzzRustService(void* binder, const uint8_t* data, size_t len);
-}
\ No newline at end of file
+    void fuzzRustService(void** binders, size_t numBinders, const uint8_t* data, size_t len);
+}
diff --git a/libs/binder/tests/IBinderRpcBenchmark.aidl b/libs/binder/tests/IBinderRpcBenchmark.aidl
index 2baf680..1008778 100644
--- a/libs/binder/tests/IBinderRpcBenchmark.aidl
+++ b/libs/binder/tests/IBinderRpcBenchmark.aidl
@@ -18,4 +18,7 @@
     @utf8InCpp String repeatString(@utf8InCpp String str);
     IBinder repeatBinder(IBinder binder);
     byte[] repeatBytes(in byte[] bytes);
+
+    IBinder gimmeBinder();
+    void waitGimmesDestroyed();
 }
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index e021af0..0396869 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -1441,6 +1441,36 @@
     EXPECT_GE(epochMsAfter, epochMsBefore + delay);
 }
 
+TEST_F(BinderLibTest, BinderProxyCount) {
+    Parcel data, reply;
+    sp<IBinder> server = addServer();
+    ASSERT_NE(server, nullptr);
+
+    uint32_t initialCount = BpBinder::getBinderProxyCount();
+    size_t iterations = 100;
+    {
+        uint32_t count = initialCount;
+        std::vector<sp<IBinder> > proxies;
+        sp<IBinder> proxy;
+        // Create binder proxies and verify the count.
+        for (size_t i = 0; i < iterations; i++) {
+            ASSERT_THAT(server->transact(BINDER_LIB_TEST_CREATE_BINDER_TRANSACTION, data, &reply),
+                        StatusEq(NO_ERROR));
+            proxies.push_back(reply.readStrongBinder());
+            EXPECT_EQ(BpBinder::getBinderProxyCount(), ++count);
+        }
+        // Remove every other one and verify the count.
+        auto it = proxies.begin();
+        for (size_t i = 0; it != proxies.end(); i++) {
+            if (i % 2 == 0) {
+                it = proxies.erase(it);
+                EXPECT_EQ(BpBinder::getBinderProxyCount(), --count);
+            }
+        }
+    }
+    EXPECT_EQ(BpBinder::getBinderProxyCount(), initialCount);
+}
+
 class BinderLibRpcTestBase : public BinderLibTest {
 public:
     void SetUp() override {
diff --git a/libs/binder/tests/binderRpcBenchmark.cpp b/libs/binder/tests/binderRpcBenchmark.cpp
index 9c96c41..4f10d74 100644
--- a/libs/binder/tests/binderRpcBenchmark.cpp
+++ b/libs/binder/tests/binderRpcBenchmark.cpp
@@ -74,6 +74,44 @@
         *out = bytes;
         return Status::ok();
     }
+
+    class CountedBinder : public BBinder {
+    public:
+        CountedBinder(const sp<MyBinderRpcBenchmark>& parent) : mParent(parent) {
+            std::lock_guard<std::mutex> l(mParent->mCountMutex);
+            mParent->mBinderCount++;
+            // std::cout << "Count + is now " << mParent->mBinderCount << std::endl;
+        }
+        ~CountedBinder() {
+            {
+                std::lock_guard<std::mutex> l(mParent->mCountMutex);
+                mParent->mBinderCount--;
+                // std::cout << "Count - is now " << mParent->mBinderCount << std::endl;
+
+                // skip notify
+                if (mParent->mBinderCount != 0) return;
+            }
+            mParent->mCountCv.notify_one();
+        }
+
+    private:
+        sp<MyBinderRpcBenchmark> mParent;
+    };
+
+    Status gimmeBinder(sp<IBinder>* out) override {
+        *out = sp<CountedBinder>::make(sp<MyBinderRpcBenchmark>::fromExisting(this));
+        return Status::ok();
+    }
+    Status waitGimmesDestroyed() override {
+        std::unique_lock<std::mutex> l(mCountMutex);
+        mCountCv.wait(l, [&] { return mBinderCount == 0; });
+        return Status::ok();
+    }
+
+    friend class CountedBinder;
+    std::mutex mCountMutex;
+    std::condition_variable mCountCv;
+    size_t mBinderCount;
 };
 
 enum Transport {
@@ -212,6 +250,38 @@
         ->ArgsProduct({kTransportList,
                        {64, 1024, 2048, 4096, 8182, 16364, 32728, 65535, 65536, 65537}});
 
+void BM_collectProxies(benchmark::State& state) {
+    sp<IBinder> binder = getBinderForOptions(state);
+    sp<IBinderRpcBenchmark> iface = interface_cast<IBinderRpcBenchmark>(binder);
+    CHECK(iface != nullptr);
+
+    const size_t kNumIters = state.range(1);
+
+    while (state.KeepRunning()) {
+        std::vector<sp<IBinder>> out;
+        out.resize(kNumIters);
+
+        for (size_t i = 0; i < kNumIters; i++) {
+            Status ret = iface->gimmeBinder(&out[i]);
+            CHECK(ret.isOk()) << ret;
+        }
+
+        out.clear();
+
+        // we are using a thread up to wait, so make a call to
+        // force all refcounts to be updated first - current
+        // binder behavior means we really don't need to wait,
+        // so code which is waiting is really there to protect
+        // against any future changes that could delay destruction
+        android::IInterface::asBinder(iface)->pingBinder();
+
+        iface->waitGimmesDestroyed();
+    }
+
+    SetLabel(state);
+}
+BENCHMARK(BM_collectProxies)->ArgsProduct({kTransportList, {10, 100, 1000, 5000, 10000, 20000}});
+
 void BM_repeatBinder(benchmark::State& state) {
     sp<IBinder> binder = getBinderForOptions(state);
     CHECK(binder != nullptr);
diff --git a/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp b/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp
index 93ac116..38e6f32 100644
--- a/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp
+++ b/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp
@@ -61,11 +61,11 @@
     while (provider.remaining_bytes() > 0) {
         // Most of the AIDL services will have small set of transaction codes.
         // TODO(b/295942369) : Add remaining transact codes from IBinder.h
-        uint32_t code = provider.ConsumeBool()
-                ? provider.ConsumeIntegral<uint32_t>()
-                : provider.PickValueInArray<int64_t>(
-                          {provider.ConsumeIntegralInRange<uint32_t>(0, 100),
-                           IBinder::DUMP_TRANSACTION, IBinder::PING_TRANSACTION,
+        uint32_t code = provider.ConsumeBool() ? provider.ConsumeIntegral<uint32_t>()
+                : provider.ConsumeBool()
+                ? provider.ConsumeIntegralInRange<uint32_t>(0, 100)
+                : provider.PickValueInArray<uint32_t>(
+                          {IBinder::DUMP_TRANSACTION, IBinder::PING_TRANSACTION,
                            IBinder::SHELL_COMMAND_TRANSACTION, IBinder::INTERFACE_TRANSACTION,
                            IBinder::SYSPROPS_TRANSACTION, IBinder::EXTENSION_TRANSACTION,
                            IBinder::TWEET_TRANSACTION, IBinder::LIKE_TRANSACTION});
diff --git a/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp b/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp
index 0b0ca34..84b9ff6 100644
--- a/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp
+++ b/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp
@@ -22,6 +22,9 @@
 // and APEX users, but we need access to it to fuzz.
 #include "../../ndk/ibinder_internal.h"
 
+using android::IBinder;
+using android::sp;
+
 namespace android {
 
 void fuzzService(const std::vector<ndk::SpAIBinder>& binders, FuzzedDataProvider&& provider) {
@@ -41,9 +44,14 @@
 
 extern "C" {
 // This API is used by fuzzers to automatically fuzz aidl services
-void fuzzRustService(void* binder, const uint8_t* data, size_t len) {
-    AIBinder* aiBinder = static_cast<AIBinder*>(binder);
+void fuzzRustService(void** binders, size_t numBinders, const uint8_t* data, size_t len) {
+    std::vector<sp<IBinder>> cppBinders;
+    for (size_t binderIndex = 0; binderIndex < numBinders; ++binderIndex) {
+        AIBinder* aiBinder = static_cast<AIBinder*>(binders[binderIndex]);
+        cppBinders.push_back(aiBinder->getBinder());
+    }
+
     FuzzedDataProvider provider(data, len);
-    android::fuzzService(aiBinder, std::move(provider));
+    android::fuzzService(cppBinders, std::move(provider));
 }
 } // extern "C"
diff --git a/libs/binder/tests/unit_fuzzers/BufferedTextOutputFuzz.cpp b/libs/binder/tests/unit_fuzzers/BufferedTextOutputFuzz.cpp
index 09cb216..b80ac53 100644
--- a/libs/binder/tests/unit_fuzzers/BufferedTextOutputFuzz.cpp
+++ b/libs/binder/tests/unit_fuzzers/BufferedTextOutputFuzz.cpp
@@ -16,6 +16,7 @@
 
 #include <commonFuzzHelpers.h>
 #include <fuzzer/FuzzedDataProvider.h>
+#include <functional>
 #include <string>
 #include <vector>
 #include "BufferedTextOutput.h"
diff --git a/libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp b/libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp
index 943fb9f..33a653e 100644
--- a/libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp
+++ b/libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp
@@ -54,7 +54,7 @@
 
     if (transaction.has_value()) {
         std::FILE* intermediateFile = std::tmpfile();
-        android::base::unique_fd fdForWriting(fileno(intermediateFile));
+        android::base::unique_fd fdForWriting(dup(fileno(intermediateFile)));
         auto writeStatus ATTRIBUTE_UNUSED = transaction.value().dumpToFile(fdForWriting);
 
         std::fclose(intermediateFile);
diff --git a/libs/bufferstreams/examples/app/Android.bp b/libs/bufferstreams/examples/app/Android.bp
new file mode 100644
index 0000000..0ecf94c
--- /dev/null
+++ b/libs/bufferstreams/examples/app/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2023 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.
+
+android_app {
+    name: "BufferStreamsDemoApp",
+    srcs: ["java/**/*.java"],
+    sdk_version: "current",
+
+    jni_uses_platform_apis: true,
+    jni_libs: ["libbufferstreamdemoapp"],
+    use_embedded_native_libs: true,
+
+    static_libs: [
+        "androidx.appcompat_appcompat",
+    ],
+}
diff --git a/libs/bufferstreams/examples/app/AndroidManifest.xml b/libs/bufferstreams/examples/app/AndroidManifest.xml
new file mode 100644
index 0000000..872193c
--- /dev/null
+++ b/libs/bufferstreams/examples/app/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.graphics.bufferstreamsdemoapp"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.AppCompat.Light"
+        tools:targetApi="34">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.java b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.java
new file mode 100644
index 0000000..67b95a5
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 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.
+
+package com.android.graphics.bufferstreamsdemoapp;
+
+import android.os.Bundle;
+import android.widget.TextView;
+import androidx.appcompat.app.AppCompatActivity;
+
+public class MainActivity extends AppCompatActivity {
+    // Used to load the 'bufferstreamsdemoapp' library on application startup.
+    static { System.loadLibrary("bufferstreamdemoapp"); }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        RunBufferQueue();
+        System.out.println("stringFromJNI: " + stringFromJNI());
+    }
+
+    /**
+     * A native method that is implemented by the 'bufferstreamsdemoapp' native
+     * library, which is packaged with this application.
+     */
+    public native String stringFromJNI();
+    public native void RunBufferQueue();
+}
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/jni/Android.bp b/libs/bufferstreams/examples/app/jni/Android.bp
new file mode 100644
index 0000000..67910a1
--- /dev/null
+++ b/libs/bufferstreams/examples/app/jni/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2023 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.
+
+cc_library_shared {
+    name: "libbufferstreamdemoapp",
+    cflags: [
+        "-Werror",
+        "-Wno-error=unused-parameter",
+    ],
+    shared_libs: [
+        "libgui",
+        "libbase",
+        "libutils",
+    ],
+    header_libs: ["jni_headers"],
+    srcs: ["*.cpp"],
+}
diff --git a/libs/bufferstreams/examples/app/jni/main.cpp b/libs/bufferstreams/examples/app/jni/main.cpp
new file mode 100644
index 0000000..34e0eb4
--- /dev/null
+++ b/libs/bufferstreams/examples/app/jni/main.cpp
@@ -0,0 +1,37 @@
+// Copyright (C) 2023 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 <jni.h>
+
+#include <gui/BufferQueue.h>
+
+extern "C"
+{
+    JNIEXPORT jstring JNICALL
+    Java_com_android_graphics_bufferstreamsdemoapp_MainActivity_stringFromJNI(
+            JNIEnv *env,
+            jobject /* this */) {
+        const char* hello = "Hello from C++";
+        return env->NewStringUTF(hello);
+    }
+
+    JNIEXPORT void JNICALL
+    Java_com_android_graphics_bufferstreamsdemoapp_MainActivity_RunBufferQueue(
+            JNIEnv *env,
+            jobject /* this */) {
+        android::sp<android::IGraphicBufferProducer> producer;
+        android::sp<android::IGraphicBufferConsumer> consumer;
+        android::BufferQueue::createBufferQueue(&producer, &consumer);
+    }
+}
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="85.84757"
+                android:endY="92.4963"
+                android:startX="42.9492"
+                android:startY="49.59793"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/layout/activity_main.xml b/libs/bufferstreams/examples/app/res/layout/activity_main.xml
new file mode 100644
index 0000000..79fb331
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/layout/activity_main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity">
+
+    <TextView
+        android:id="@+id/sample_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Hello World!"
+        tools:layout_editor_absoluteX="100dp"
+        tools:layout_editor_absoluteY="100dp" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/values/colors.xml b/libs/bufferstreams/examples/app/res/values/colors.xml
new file mode 100644
index 0000000..f8c6127
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+</resources>
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/values/strings.xml b/libs/bufferstreams/examples/app/res/values/strings.xml
new file mode 100644
index 0000000..e652102
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">Buffer Demos</string>
+</resources>
\ No newline at end of file
diff --git a/libs/bufferstreams/rust/Android.bp b/libs/bufferstreams/rust/Android.bp
index ff95148..7fcb222 100644
--- a/libs/bufferstreams/rust/Android.bp
+++ b/libs/bufferstreams/rust/Android.bp
@@ -12,13 +12,26 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+rust_defaults {
+    name: "libbufferstreams_defaults",
+    srcs: ["src/lib.rs"],
+    rustlibs: [
+        "libanyhow",
+        "libnativewindow_rs",
+    ],
+    edition: "2021",
+}
+
 rust_library {
     name: "libbufferstreams",
     crate_name: "bufferstreams",
-    srcs: ["src/lib.rs"],
-    edition: "2021",
-    rlibs: [
-        "libnativewindow_rs",
-    ],
+    defaults: ["libbufferstreams_defaults"],
     min_sdk_version: "30",
 }
+
+rust_test {
+    name: "libbufferstreams-internal_test",
+    crate_name: "bufferstreams",
+    defaults: ["libbufferstreams_defaults"],
+    test_suites: ["general-tests"],
+}
diff --git a/libs/bufferstreams/rust/src/lib.rs b/libs/bufferstreams/rust/src/lib.rs
index 1d321c8..87f3104 100644
--- a/libs/bufferstreams/rust/src/lib.rs
+++ b/libs/bufferstreams/rust/src/lib.rs
@@ -14,8 +14,14 @@
 
 //! libbufferstreams: Reactive Streams for Graphics Buffers
 
+pub mod publishers;
+mod stream_config;
+pub mod subscribers;
+pub mod subscriptions;
+
+pub use stream_config::*;
+
 use nativewindow::*;
-use std::sync::{Arc, Weak};
 use std::time::Instant;
 
 /// This function will print Hello World.
@@ -31,28 +37,30 @@
 ///
 /// BufferPublishers are required to adhere to the following, based on the
 /// reactive streams specification:
-/// *   The total number of on_next´s signalled by a Publisher to a Subscriber
+/// * The total number of on_next´s signalled by a Publisher to a Subscriber
 /// MUST be less than or equal to the total number of elements requested by that
 /// Subscriber´s Subscription at all times.
-/// *   A Publisher MAY signal fewer on_next than requested and terminate the
+/// * A Publisher MAY signal fewer on_next than requested and terminate the
 /// Subscription by calling on_complete or on_error.
-/// *   on_subscribe, on_next, on_error and on_complete signaled to a Subscriber
+/// * on_subscribe, on_next, on_error and on_complete signaled to a Subscriber
 /// MUST be signaled serially.
-/// *   If a Publisher fails it MUST signal an on_error.
-/// *   If a Publisher terminates successfully (finite stream) it MUST signal an
+/// * If a Publisher fails it MUST signal an on_error.
+/// * If a Publisher terminates successfully (finite stream) it MUST signal an
 /// on_complete.
-/// *   If a Publisher signals either on_error or on_complete on a Subscriber,
+/// * If a Publisher signals either on_error or on_complete on a Subscriber,
 /// that Subscriber’s Subscription MUST be considered cancelled.
-/// *   Once a terminal state has been signaled (on_error, on_complete) it is
+/// * Once a terminal state has been signaled (on_error, on_complete) it is
 /// REQUIRED that no further signals occur.
-/// *   If a Subscription is cancelled its Subscriber MUST eventually stop being
+/// * If a Subscription is cancelled its Subscriber MUST eventually stop being
 ///  signaled.
-/// *  A Publisher MAY support multiple Subscribers and decides whether each
+/// * A Publisher MAY support multiple Subscribers and decides whether each
 /// Subscription is unicast or multicast.
 pub trait BufferPublisher {
+    /// Returns the StreamConfig of buffers that publisher creates.
+    fn get_publisher_stream_config(&self) -> StreamConfig;
     /// This function will create the subscription between the publisher and
     /// the subscriber.
-    fn subscribe(&self, subscriber: Weak<dyn BufferSubscriber>);
+    fn subscribe(&mut self, subscriber: impl BufferSubscriber + 'static);
 }
 
 /// BufferSubscribers can subscribe to BufferPublishers. They can request Frames
@@ -61,35 +69,37 @@
 ///
 /// BufferSubcribers are required to adhere to the following, based on the
 /// reactive streams specification:
-/// *   The total number of on_next´s signalled by a Publisher to a Subscriber
+/// * The total number of on_next´s signalled by a Publisher to a Subscriber
 /// MUST be less than or equal to the total number of elements requested by that
 /// Subscriber´s Subscription at all times.
-/// *   A Publisher MAY signal fewer on_next than requested and terminate the
+/// * A Publisher MAY signal fewer on_next than requested and terminate the
 /// Subscription by calling on_complete or on_error.
-/// *   on_subscribe, on_next, on_error and on_complete signaled to a Subscriber
+/// * on_subscribe, on_next, on_error and on_complete signaled to a Subscriber
 /// MUST be signaled serially.
-/// *   If a Publisher fails it MUST signal an on_error.
-/// *   If a Publisher terminates successfully (finite stream) it MUST signal an
+/// * If a Publisher fails it MUST signal an on_error.
+/// * If a Publisher terminates successfully (finite stream) it MUST signal an
 /// on_complete.
-/// *   If a Publisher signals either on_error or on_complete on a Subscriber,
+/// * If a Publisher signals either on_error or on_complete on a Subscriber,
 /// that Subscriber’s Subscription MUST be considered cancelled.
-/// *   Once a terminal state has been signaled (on_error, on_complete) it is
+/// * Once a terminal state has been signaled (on_error, on_complete) it is
 /// REQUIRED that no further signals occur.
-/// *   If a Subscription is cancelled its Subscriber MUST eventually stop being
+/// * If a Subscription is cancelled its Subscriber MUST eventually stop being
 /// signaled.
-/// *   Publisher.subscribe MAY be called as many times as wanted but MUST be
+/// * Publisher.subscribe MAY be called as many times as wanted but MUST be
 /// with a different Subscriber each time.
-/// *   A Publisher MAY support multiple Subscribers and decides whether each
+/// * A Publisher MAY support multiple Subscribers and decides whether each
 /// Subscription is unicast or multicast.
 pub trait BufferSubscriber {
+    /// The StreamConfig of buffers that this subscriber expects.
+    fn get_subscriber_stream_config(&self) -> StreamConfig;
     /// This function will be called at the beginning of the subscription.
-    fn on_subscribe(&self, subscription: Arc<dyn BufferSubscription>);
+    fn on_subscribe(&mut self, subscription: Box<dyn BufferSubscription>);
     /// This function will be called for buffer that comes in.
-    fn on_next(&self, frame: Frame);
+    fn on_next(&mut self, frame: Frame);
     /// This function will be called in case of an error.
-    fn on_error(&self, error: BufferError);
+    fn on_error(&mut self, error: BufferError);
     /// This function will be called on finite streams when done.
-    fn on_complete(&self);
+    fn on_complete(&mut self);
 }
 
 /// BufferSubscriptions serve as the bridge between BufferPublishers and
@@ -100,50 +110,51 @@
 ///
 /// BufferSubcriptions are required to adhere to the following, based on the
 /// reactive streams specification:
-/// *   Subscription.request and Subscription.cancel MUST only be called inside
+/// * Subscription.request and Subscription.cancel MUST only be called inside
 /// of its Subscriber context.
-/// *   The Subscription MUST allow the Subscriber to call Subscription.request
+/// * The Subscription MUST allow the Subscriber to call Subscription.request
 /// synchronously from within on_next or on_subscribe.
-/// *   Subscription.request MUST place an upper bound on possible synchronous
+/// * Subscription.request MUST place an upper bound on possible synchronous
 /// recursion between Publisher and Subscriber.
-/// *   Subscription.request SHOULD respect the responsivity of its caller by
+/// * Subscription.request SHOULD respect the responsivity of its caller by
 /// returning in a timely manner.
-/// *   Subscription.cancel MUST respect the responsivity of its caller by
+/// * Subscription.cancel MUST respect the responsivity of its caller by
 /// returning in a timely manner, MUST be idempotent and MUST be thread-safe.
-/// *   After the Subscription is cancelled, additional
+/// * After the Subscription is cancelled, additional
 /// Subscription.request(n: u64) MUST be NOPs.
-/// *   After the Subscription is cancelled, additional Subscription.cancel()
+/// * After the Subscription is cancelled, additional Subscription.cancel()
 /// MUST be NOPs.
-/// *   While the Subscription is not cancelled, Subscription.request(n: u64)
+/// * While the Subscription is not cancelled, Subscription.request(n: u64)
 /// MUST register the given number of additional elements to be produced to the
 /// respective subscriber.
-/// *   While the Subscription is not cancelled, Subscription.request(n: u64)
+/// * While the Subscription is not cancelled, Subscription.request(n: u64)
 /// MUST signal on_error if the argument is <= 0. The cause message SHOULD
 /// explain that non-positive request signals are illegal.
-/// *  While the Subscription is not cancelled, Subscription.request(n: u64)
+/// * While the Subscription is not cancelled, Subscription.request(n: u64)
 /// MAY synchronously call on_next on this (or other) subscriber(s).
-/// *  While the Subscription is not cancelled, Subscription.request(n: u64)
+/// * While the Subscription is not cancelled, Subscription.request(n: u64)
 /// MAY synchronously call on_complete or on_error on this (or other)
 /// subscriber(s).
-/// *  While the Subscription is not cancelled, Subscription.cancel() MUST
+/// * While the Subscription is not cancelled, Subscription.cancel() MUST
 /// request the Publisher to eventually stop signaling its Subscriber. The
 /// operation is NOT REQUIRED to affect the Subscription immediately.
-/// *  While the Subscription is not cancelled, Subscription.cancel() MUST
+/// * While the Subscription is not cancelled, Subscription.cancel() MUST
 /// request the Publisher to eventually drop any references to the corresponding
 /// subscriber.
-/// *  While the Subscription is not cancelled, calling Subscription.cancel MAY
+/// * While the Subscription is not cancelled, calling Subscription.cancel MAY
 /// cause the Publisher, if stateful, to transition into the shut-down state if
 /// no other Subscription exists at this point.
-/// *  Calling Subscription.cancel MUST return normally.
-/// *  Calling Subscription.request MUST return normally.
+/// * Calling Subscription.cancel MUST return normally.
+/// * Calling Subscription.request MUST return normally.
 pub trait BufferSubscription {
     /// request
     fn request(&self, n: u64);
     /// cancel
     fn cancel(&self);
 }
+
 /// Type used to describe errors produced by subscriptions.
-type BufferError = Box<dyn std::error::Error + Send + Sync + 'static>;
+pub type BufferError = anyhow::Error;
 
 /// Struct used to contain the buffer.
 pub struct Frame {
@@ -154,3 +165,88 @@
     /// A fence used for reading/writing safely.
     pub fence: i32,
 }
+
+#[cfg(test)]
+mod test {
+    #![allow(warnings, unused)]
+    use super::*;
+
+    use anyhow::anyhow;
+    use std::borrow::BorrowMut;
+    use std::error::Error;
+    use std::ops::Add;
+    use std::sync::Arc;
+    use std::time::Duration;
+
+    use crate::publishers::testing::*;
+    use crate::subscribers::{testing::*, SharedSubscriber};
+
+    const STREAM_CONFIG: StreamConfig = StreamConfig {
+        width: 1,
+        height: 1,
+        layers: 1,
+        format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+        usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+        stride: 0,
+    };
+
+    fn make_frame() -> Frame {
+        Frame {
+            buffer: STREAM_CONFIG
+                .create_hardware_buffer()
+                .expect("Unable to create hardware buffer for test"),
+            present_time: Instant::now() + Duration::from_secs(1),
+            fence: 0,
+        }
+    }
+
+    #[test]
+    fn test_test_implementations_next() {
+        let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG));
+        let mut publisher = TestPublisher::new(STREAM_CONFIG);
+
+        publisher.subscribe(subscriber.clone());
+        assert!(subscriber.map_inner(|s| s.has_subscription()));
+        assert!(publisher.has_subscriber());
+
+        publisher.send_frame(make_frame());
+        let events = subscriber.map_inner_mut(|s| s.take_events());
+        assert!(!matches!(events.last().unwrap(), TestingSubscriberEvent::Next(_)));
+
+        subscriber.map_inner(|s| s.request(1));
+        assert_eq!(publisher.pending_requests(), 1);
+
+        publisher.send_frame(make_frame());
+        let events = subscriber.map_inner_mut(|s| s.take_events());
+        assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Next(_)));
+        assert_eq!(publisher.pending_requests(), 0);
+    }
+
+    #[test]
+    fn test_test_implementations_complete() {
+        let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG));
+        let mut publisher = TestPublisher::new(STREAM_CONFIG);
+
+        publisher.subscribe(subscriber.clone());
+        assert!(subscriber.map_inner(|s| s.has_subscription()));
+        assert!(publisher.has_subscriber());
+
+        publisher.send_complete();
+        let events = subscriber.map_inner_mut(|s| s.take_events());
+        assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Complete));
+    }
+
+    #[test]
+    fn test_test_implementations_error() {
+        let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG));
+        let mut publisher = TestPublisher::new(STREAM_CONFIG);
+
+        publisher.subscribe(subscriber.clone());
+        assert!(subscriber.map_inner(|s| s.has_subscription()));
+        assert!(publisher.has_subscriber());
+
+        publisher.send_error(anyhow!("error"));
+        let events = subscriber.map_inner_mut(|s| s.take_events());
+        assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Error(_)));
+    }
+}
diff --git a/libs/bufferstreams/rust/src/publishers/mod.rs b/libs/bufferstreams/rust/src/publishers/mod.rs
new file mode 100644
index 0000000..2fd518e
--- /dev/null
+++ b/libs/bufferstreams/rust/src/publishers/mod.rs
@@ -0,0 +1,17 @@
+// Copyright (C) 2023 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.
+
+//! This module provides [BufferSubscriber] implementations and helpers.
+
+pub mod testing;
diff --git a/libs/bufferstreams/rust/src/publishers/testing.rs b/libs/bufferstreams/rust/src/publishers/testing.rs
new file mode 100644
index 0000000..1593b18
--- /dev/null
+++ b/libs/bufferstreams/rust/src/publishers/testing.rs
@@ -0,0 +1,103 @@
+// Copyright (C) 2023 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.
+
+//! Provides useful publishers for testing specifically. These should not be used in normal code.
+
+use crate::{subscriptions::SharedBufferSubscription, *};
+
+/// A [BufferPublisher] specifically for testing.
+///
+/// Provides users the ability to send events and read the state of the subscription.
+pub struct TestPublisher {
+    config: StreamConfig,
+    subscriber: Option<Box<dyn BufferSubscriber>>,
+    subscription: SharedBufferSubscription,
+}
+
+impl TestPublisher {
+    /// Create a new [TestPublisher].
+    pub fn new(config: StreamConfig) -> Self {
+        Self { config, subscriber: None, subscription: SharedBufferSubscription::new() }
+    }
+
+    /// Send a [BufferSubscriber::on_next] event to an owned [BufferSubscriber] if it has any
+    /// requested and returns true. Drops the frame and returns false otherwise.
+    ///
+    /// # Panics
+    ///
+    /// This will panic if there is no owned subscriber.
+    pub fn send_frame(&mut self, frame: Frame) -> bool {
+        let subscriber =
+            self.subscriber.as_deref_mut().expect("Tried to send_frame with no subscriber");
+
+        if self.subscription.take_request() {
+            subscriber.on_next(frame);
+            true
+        } else {
+            false
+        }
+    }
+
+    /// Send a [BufferSubscriber::on_complete] event to an owned [BufferSubscriber].
+    ///
+    /// # Panics
+    ///
+    /// This will panic if there is no owned subscriber.
+    pub fn send_complete(&mut self) {
+        let subscriber =
+            self.subscriber.as_deref_mut().expect("Tried to send_complete with no subscriber");
+        subscriber.on_complete();
+    }
+
+    /// Send a [BufferSubscriber::on_error] event to an owned [BufferSubscriber].
+    ///
+    /// # Panics
+    ///
+    /// This will panic if there is no owned subscriber.
+    pub fn send_error(&mut self, error: BufferError) {
+        let subscriber =
+            self.subscriber.as_deref_mut().expect("Tried to send_error with no subscriber");
+        subscriber.on_error(error);
+    }
+
+    /// Returns whether this [BufferPublisher] owns a subscriber.
+    pub fn has_subscriber(&self) -> bool {
+        self.subscriber.is_some()
+    }
+
+    /// Returns the nummber of frames requested by the [BufferSubscriber].
+    pub fn pending_requests(&self) -> u64 {
+        self.subscription.pending_requests()
+    }
+
+    /// Returns whether the [BufferSubscriber] has cancelled the subscription.
+    pub fn is_cancelled(&self) -> bool {
+        self.subscription.is_cancelled()
+    }
+}
+
+impl BufferPublisher for TestPublisher {
+    fn get_publisher_stream_config(&self) -> crate::StreamConfig {
+        self.config
+    }
+
+    fn subscribe(&mut self, subscriber: impl BufferSubscriber + 'static) {
+        assert!(self.subscriber.is_none(), "TestingPublishers can only take one subscriber");
+        self.subscriber = Some(Box::new(subscriber));
+
+        if let Some(ref mut subscriber) = self.subscriber {
+            subscriber.on_subscribe(self.subscription.clone_for_subscriber());
+        }
+    }
+}
diff --git a/libs/bufferstreams/rust/src/stream_config.rs b/libs/bufferstreams/rust/src/stream_config.rs
new file mode 100644
index 0000000..d0c621b
--- /dev/null
+++ b/libs/bufferstreams/rust/src/stream_config.rs
@@ -0,0 +1,67 @@
+// Copyright (C) 2023 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.
+
+use nativewindow::*;
+
+/// The configuration of the buffers published by a [BufferPublisher] or
+/// expected by a [BufferSubscriber].
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct StreamConfig {
+    /// Width in pixels of streaming buffers.
+    pub width: u32,
+    /// Height in pixels of streaming buffers.
+    pub height: u32,
+    /// Number of layers of streaming buffers.
+    pub layers: u32,
+    /// Format of streaming buffers.
+    pub format: AHardwareBuffer_Format::Type,
+    /// Usage of streaming buffers.
+    pub usage: AHardwareBuffer_UsageFlags,
+    /// Stride of streaming buffers.
+    pub stride: u32,
+}
+
+impl StreamConfig {
+    /// Tries to create a new AHardwareBuffer from settings in a [StreamConfig].
+    pub fn create_hardware_buffer(&self) -> Option<AHardwareBuffer> {
+        AHardwareBuffer::new(self.width, self.height, self.layers, self.format, self.usage)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_create_hardware_buffer() {
+        let config = StreamConfig {
+            width: 123,
+            height: 456,
+            layers: 1,
+            format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN
+                | AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
+            stride: 0,
+        };
+
+        let maybe_buffer = config.create_hardware_buffer();
+        assert!(maybe_buffer.is_some());
+
+        let buffer = maybe_buffer.unwrap();
+        assert_eq!(config.width, buffer.width());
+        assert_eq!(config.height, buffer.height());
+        assert_eq!(config.format, buffer.format());
+        assert_eq!(config.usage, buffer.usage());
+    }
+}
diff --git a/libs/bufferstreams/rust/src/subscribers/mod.rs b/libs/bufferstreams/rust/src/subscribers/mod.rs
new file mode 100644
index 0000000..dd038c6
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscribers/mod.rs
@@ -0,0 +1,20 @@
+// Copyright (C) 2023 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.
+
+//! This module provides [BufferSubscriber] implementations and helpers.
+
+mod shared;
+pub mod testing;
+
+pub use shared::*;
diff --git a/libs/bufferstreams/rust/src/subscribers/shared.rs b/libs/bufferstreams/rust/src/subscribers/shared.rs
new file mode 100644
index 0000000..46c58dc
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscribers/shared.rs
@@ -0,0 +1,94 @@
+// Copyright (C) 2023 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.
+
+//! This module provides [BufferSubscriber] implementations and helpers.
+
+use std::sync::{Arc, Mutex};
+
+use crate::*;
+
+/// A [BufferSubscriber] wrapper that provides shared access.
+///
+/// Normally, [BufferSubscriber]s are fully owned by the publisher that they are attached to. With
+/// [SharedSubscriber], a
+///
+/// # Panics
+///
+/// [BufferSubscriber::on_subscribe] on a [SharedSubscriber] can only be called once, otherwise it
+/// will panic. This is to prevent accidental and unsupported sharing between multiple publishers to
+/// reflect the usual behavior where a publisher takes full ownership of a subscriber.
+pub struct SharedSubscriber<S: BufferSubscriber>(Arc<Mutex<SharedSubscriberInner<S>>>);
+
+struct SharedSubscriberInner<S: BufferSubscriber> {
+    subscriber: S,
+    is_subscribed: bool,
+}
+
+impl<S: BufferSubscriber> SharedSubscriber<S> {
+    /// Create a new wrapper around a [BufferSubscriber].
+    pub fn new(subscriber: S) -> Self {
+        Self(Arc::new(Mutex::new(SharedSubscriberInner { subscriber, is_subscribed: false })))
+    }
+
+    /// Provides access to an immutable reference to the wrapped [BufferSubscriber].
+    pub fn map_inner<R, F: FnOnce(&S) -> R>(&self, f: F) -> R {
+        let inner = self.0.lock().unwrap();
+        f(&inner.subscriber)
+    }
+
+    /// Provides access to a mutable reference to the wrapped [BufferSubscriber].
+    pub fn map_inner_mut<R, F: FnOnce(&mut S) -> R>(&self, f: F) -> R {
+        let mut inner = self.0.lock().unwrap();
+        f(&mut inner.subscriber)
+    }
+}
+
+impl<S: BufferSubscriber> Clone for SharedSubscriber<S> {
+    fn clone(&self) -> Self {
+        Self(Arc::clone(&self.0))
+    }
+}
+
+impl<S: BufferSubscriber> BufferSubscriber for SharedSubscriber<S> {
+    fn get_subscriber_stream_config(&self) -> StreamConfig {
+        let inner = self.0.lock().unwrap();
+        inner.subscriber.get_subscriber_stream_config()
+    }
+
+    fn on_subscribe(&mut self, subscription: Box<dyn BufferSubscription>) {
+        let mut inner = self.0.lock().unwrap();
+        assert!(
+            !inner.is_subscribed,
+            "A SharedSubscriber can not be shared between two BufferPublishers"
+        );
+        inner.is_subscribed = true;
+
+        inner.subscriber.on_subscribe(subscription);
+    }
+
+    fn on_next(&mut self, frame: Frame) {
+        let mut inner = self.0.lock().unwrap();
+        inner.subscriber.on_next(frame);
+    }
+
+    fn on_error(&mut self, error: BufferError) {
+        let mut inner = self.0.lock().unwrap();
+        inner.subscriber.on_error(error);
+    }
+
+    fn on_complete(&mut self) {
+        let mut inner = self.0.lock().unwrap();
+        inner.subscriber.on_complete();
+    }
+}
diff --git a/libs/bufferstreams/rust/src/subscribers/testing.rs b/libs/bufferstreams/rust/src/subscribers/testing.rs
new file mode 100644
index 0000000..b7e9705
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscribers/testing.rs
@@ -0,0 +1,106 @@
+// Copyright (C) 2023 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.
+
+//! Provides useful subscribers for testing specifically. These should not be used in normal code.
+
+use crate::*;
+
+/// Represents a callback called by a [BufferPublisher] on a [BufferSubscriber].
+pub enum TestingSubscriberEvent {
+    /// Represents a call to [BufferSubscriber::on_subscribe].
+    Subscribe,
+    /// Represents a call to [BufferSubscriber::on_next].
+    Next(Frame),
+    /// Represents a call to [BufferSubscriber::on_error].
+    Error(BufferError),
+    /// Represents a call to [BufferSubscriber::on_complete].
+    Complete,
+}
+
+/// A [BufferSubscriber] specifically for testing. Logs events as they happen which can be retrieved
+/// by the test to ensure appropriate behavior.
+pub struct TestSubscriber {
+    config: StreamConfig,
+    subscription: Option<Box<dyn BufferSubscription>>,
+    events: Vec<TestingSubscriberEvent>,
+}
+
+impl TestSubscriber {
+    /// Create a new [TestSubscriber].
+    pub fn new(config: StreamConfig) -> Self {
+        Self { config, subscription: None, events: Vec::new() }
+    }
+
+    /// Returns true if this [BufferSubscriber] has an active subscription.
+    pub fn has_subscription(&self) -> bool {
+        self.subscription.is_some()
+    }
+
+    /// Make a request on behalf of this test subscriber.
+    ///
+    /// This will panic if there is no owned subscription.
+    pub fn request(&self, n: u64) {
+        let subscription = self
+            .subscription
+            .as_deref()
+            .expect("Tried to request on a TestSubscriber with no subscription");
+        subscription.request(n);
+    }
+
+    /// Cancel on behalf of this test subscriber.
+    ///
+    /// # Panics
+    ///
+    /// This will panic if there is no owned subscription.
+    pub fn cancel(&self) {
+        let subscription = self
+            .subscription
+            .as_deref()
+            .expect("Tried to cancel a TestSubscriber with no subscription");
+        subscription.cancel();
+    }
+
+    /// Gets all of the events that have happened to this [BufferSubscriber] since the last call
+    /// to this function or it was created.
+    pub fn take_events(&mut self) -> Vec<TestingSubscriberEvent> {
+        let mut out = Vec::new();
+        out.append(&mut self.events);
+        out
+    }
+}
+
+impl BufferSubscriber for TestSubscriber {
+    fn get_subscriber_stream_config(&self) -> StreamConfig {
+        self.config
+    }
+
+    fn on_subscribe(&mut self, subscription: Box<dyn BufferSubscription>) {
+        assert!(self.subscription.is_none(), "TestSubscriber must only be subscribed to once");
+        self.subscription = Some(subscription);
+
+        self.events.push(TestingSubscriberEvent::Subscribe);
+    }
+
+    fn on_next(&mut self, frame: Frame) {
+        self.events.push(TestingSubscriberEvent::Next(frame));
+    }
+
+    fn on_error(&mut self, error: BufferError) {
+        self.events.push(TestingSubscriberEvent::Error(error));
+    }
+
+    fn on_complete(&mut self) {
+        self.events.push(TestingSubscriberEvent::Complete);
+    }
+}
diff --git a/libs/bufferstreams/rust/src/subscriptions/mod.rs b/libs/bufferstreams/rust/src/subscriptions/mod.rs
new file mode 100644
index 0000000..e046dbb
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscriptions/mod.rs
@@ -0,0 +1,19 @@
+// Copyright (C) 2023 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.
+
+//! This module provides [BufferSubscription] implementations and helpers.
+
+mod shared_buffer_subscription;
+
+pub use shared_buffer_subscription::*;
diff --git a/libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs b/libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs
new file mode 100644
index 0000000..90275c7
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs
@@ -0,0 +1,84 @@
+// Copyright (C) 2023 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.
+
+use std::sync::{Arc, Mutex};
+
+use crate::*;
+
+/// A simple sharable helper that can be used as a [BufferSubscription] by a [BufferSubscriber] and
+/// as a state tracker by a [BufferPublisher].
+#[derive(Clone, Debug)]
+pub struct SharedBufferSubscription(Arc<Mutex<BufferSubscriptionData>>);
+
+#[derive(Debug, Default)]
+struct BufferSubscriptionData {
+    requests: u64,
+    is_cancelled: bool,
+}
+
+impl SharedBufferSubscription {
+    /// Create a new [SharedBufferSubscription].
+    pub fn new() -> Self {
+        SharedBufferSubscription::default()
+    }
+
+    /// Clone this [SharedBufferSubscription] so it can be passed into
+    /// [BufferSubscriber::on_subscribe].
+    pub fn clone_for_subscriber(&self) -> Box<dyn BufferSubscription> {
+        Box::new(self.clone()) as Box<dyn BufferSubscription>
+    }
+
+    /// If possible (not cancelled and with requests pending), take
+    pub fn take_request(&self) -> bool {
+        let mut data = self.0.lock().unwrap();
+
+        if data.is_cancelled || data.requests == 0 {
+            false
+        } else {
+            data.requests -= 1;
+            true
+        }
+    }
+
+    /// Get the number of pending requests made by the [BufferSubscriber] via
+    /// [BufferSubscription::request].
+    pub fn pending_requests(&self) -> u64 {
+        self.0.lock().unwrap().requests
+    }
+
+    /// Get get whether the [BufferSubscriber] has called [BufferSubscription::cancel].
+    pub fn is_cancelled(&self) -> bool {
+        self.0.lock().unwrap().is_cancelled
+    }
+}
+
+impl Default for SharedBufferSubscription {
+    fn default() -> Self {
+        Self(Arc::new(Mutex::new(BufferSubscriptionData::default())))
+    }
+}
+
+impl BufferSubscription for SharedBufferSubscription {
+    fn request(&self, n: u64) {
+        let mut data = self.0.lock().unwrap();
+        if !data.is_cancelled {
+            data.requests = data.requests.saturating_add(n);
+        }
+    }
+
+    fn cancel(&self) {
+        let mut data = self.0.lock().unwrap();
+        data.is_cancelled = true;
+    }
+}
diff --git a/libs/cputimeinstate/fuzz/cputimeinstate_fuzzer/cputimeinstate_fuzzer.cpp b/libs/cputimeinstate/fuzz/cputimeinstate_fuzzer/cputimeinstate_fuzzer.cpp
index f835997..9df5632 100644
--- a/libs/cputimeinstate/fuzz/cputimeinstate_fuzzer/cputimeinstate_fuzzer.cpp
+++ b/libs/cputimeinstate/fuzz/cputimeinstate_fuzzer/cputimeinstate_fuzzer.cpp
@@ -20,6 +20,7 @@
 #include <fuzzer/FuzzedDataProvider.h>
 #include <android-base/unique_fd.h>
 #include <cputimeinstate.h>
+#include <functional>
 
 using namespace android::bpf;
 
diff --git a/libs/ftl/OWNERS b/libs/ftl/OWNERS
new file mode 100644
index 0000000..3f61292
--- /dev/null
+++ b/libs/ftl/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/native:/services/surfaceflinger/OWNERS
\ No newline at end of file
diff --git a/libs/ftl/enum_test.cpp b/libs/ftl/enum_test.cpp
index 5592a01..b68c2c3 100644
--- a/libs/ftl/enum_test.cpp
+++ b/libs/ftl/enum_test.cpp
@@ -33,6 +33,11 @@
 static_assert(ftl::enum_name(E::C).value_or("?") == "C");
 static_assert(ftl::enum_name(E{3}).value_or("?") == "?");
 
+static_assert(ftl::enum_name_full<E::B>() == "E::B");
+static_assert(ftl::enum_name_full<E::ftl_last>() == "E::F");
+static_assert(ftl::enum_name_full(E::C).value_or("?") == "E::C");
+static_assert(ftl::enum_name_full(E{3}).value_or("?") == "?");
+
 enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
 
 static_assert(ftl::enum_begin_v<F> == F{0});
@@ -60,6 +65,10 @@
 static_assert(ftl::enum_name<Flags::kFlag4>() == "kFlag4");
 static_assert(ftl::enum_name<Flags::kFlag7>() == "kFlag7");
 
+static_assert(ftl::enum_name_full<Flags::kNone>() == "Flags::kNone");
+static_assert(ftl::enum_name_full<Flags::kFlag4>() == "Flags::kFlag4");
+static_assert(ftl::enum_name_full<Flags::kFlag7>() == "Flags::kFlag7");
+
 // Though not flags, the enumerators are within the implicit range of bit indices.
 enum class Planet : std::uint8_t {
   kMercury,
@@ -81,6 +90,9 @@
 static_assert(ftl::enum_name<Planet::kMercury>() == "kMercury");
 static_assert(ftl::enum_name<Planet::kSaturn>() == "kSaturn");
 
+static_assert(ftl::enum_name_full<Planet::kMercury>() == "Planet::kMercury");
+static_assert(ftl::enum_name_full<Planet::kSaturn>() == "Planet::kSaturn");
+
 // Unscoped enum must define explicit range, even if the underlying type is fixed.
 enum Temperature : int {
   kRoom = 20,
@@ -122,16 +134,28 @@
     EXPECT_EQ(ftl::enum_name(Planet::kEarth), "kEarth");
     EXPECT_EQ(ftl::enum_name(Planet::kNeptune), "kNeptune");
 
+    EXPECT_EQ(ftl::enum_name_full(Planet::kEarth), "Planet::kEarth");
+    EXPECT_EQ(ftl::enum_name_full(Planet::kNeptune), "Planet::kNeptune");
+
     EXPECT_EQ(ftl::enum_name(kPluto), std::nullopt);
+    EXPECT_EQ(ftl::enum_name_full(kPluto), std::nullopt);
   }
   {
     EXPECT_EQ(ftl::enum_name(kRoom), "kRoom");
     EXPECT_EQ(ftl::enum_name(kFridge), "kFridge");
     EXPECT_EQ(ftl::enum_name(kFreezer), "kFreezer");
 
+    EXPECT_EQ(ftl::enum_name(kRoom), "kRoom");
+    EXPECT_EQ(ftl::enum_name(kFridge), "kFridge");
+    EXPECT_EQ(ftl::enum_name(kFreezer), "kFreezer");
+
     EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(-30)), std::nullopt);
     EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(0)), std::nullopt);
     EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(100)), std::nullopt);
+
+    EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(-30)), std::nullopt);
+    EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(0)), std::nullopt);
+    EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(100)), std::nullopt);
   }
 }
 
@@ -158,16 +182,30 @@
     EXPECT_EQ(ftl::enum_string(Planet::kEarth), "kEarth");
     EXPECT_EQ(ftl::enum_string(Planet::kNeptune), "kNeptune");
 
+    EXPECT_EQ(ftl::enum_string_full(Planet::kEarth), "Planet::kEarth");
+    EXPECT_EQ(ftl::enum_string_full(Planet::kNeptune), "Planet::kNeptune");
+
     EXPECT_EQ(ftl::enum_string(kPluto), "8");
+
+    EXPECT_EQ(ftl::enum_string_full(kPluto), "8");
+
   }
   {
     EXPECT_EQ(ftl::enum_string(kRoom), "kRoom");
     EXPECT_EQ(ftl::enum_string(kFridge), "kFridge");
     EXPECT_EQ(ftl::enum_string(kFreezer), "kFreezer");
 
+    EXPECT_EQ(ftl::enum_string_full(kRoom), "20");
+    EXPECT_EQ(ftl::enum_string_full(kFridge), "4");
+    EXPECT_EQ(ftl::enum_string_full(kFreezer), "-18");
+
     EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(-30)), "-30");
     EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(0)), "0");
     EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(100)), "100");
+
+    EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(-30)), "-30");
+    EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(0)), "0");
+    EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(100)), "100");
   }
 }
 
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 2ea4d16..9a27d23 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -204,27 +204,8 @@
     aconfig_declarations: "libgui_flags",
 }
 
-cc_library_shared {
-    name: "libgui",
-    vendor_available: true,
-    vndk: {
-        enabled: true,
-        private: true,
-    },
-    double_loadable: true,
-
-    defaults: ["libgui_bufferqueue-defaults"],
-
-    static_libs: [
-        "libgui_aidl_static",
-        "libgui_window_info_static",
-        "libguiflags",
-    ],
-    export_static_lib_headers: [
-        "libgui_aidl_static",
-        "libgui_window_info_static",
-    ],
-
+filegroup {
+    name: "libgui-sources",
     srcs: [
         ":framework_native_aidl_binder",
         ":framework_native_aidl_gui",
@@ -268,11 +249,40 @@
         "bufferqueue/2.0/B2HProducerListener.cpp",
         "bufferqueue/2.0/H2BGraphicBufferProducer.cpp",
     ],
+}
 
+cc_defaults {
+    name: "libgui-defaults",
+    defaults: ["libgui_bufferqueue-defaults"],
+    srcs: [":libgui-sources"],
+    static_libs: [
+        "libgui_aidl_static",
+        "libgui_window_info_static",
+        "libguiflags",
+    ],
     shared_libs: [
         "libbinder",
         "libGLESv2",
     ],
+}
+
+cc_library_shared {
+    name: "libgui",
+    vendor_available: true,
+    vndk: {
+        enabled: true,
+        private: true,
+    },
+    double_loadable: true,
+
+    defaults: [
+        "libgui-defaults",
+    ],
+
+    export_static_lib_headers: [
+        "libgui_aidl_static",
+        "libgui_window_info_static",
+    ],
 
     export_shared_lib_headers: [
         "libbinder",
@@ -346,6 +356,7 @@
         "BufferQueueProducer.cpp",
         "BufferQueueThreadState.cpp",
         "BufferSlot.cpp",
+        "FrameRateUtils.cpp",
         "FrameTimestamps.cpp",
         "GLConsumerUtils.cpp",
         "HdrMetadata.cpp",
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 207fa4f..dd0a028 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -26,6 +26,8 @@
 #include <gui/BufferQueueConsumer.h>
 #include <gui/BufferQueueCore.h>
 #include <gui/BufferQueueProducer.h>
+#include <gui/Flags.h>
+#include <gui/FrameRateUtils.h>
 #include <gui/GLConsumer.h>
 #include <gui/IProducerListener.h>
 #include <gui/Surface.h>
@@ -39,6 +41,9 @@
 #include <android-base/thread_annotations.h>
 #include <chrono>
 
+#include <com_android_graphics_libgui_flags.h>
+
+using namespace com::android::graphics::libgui;
 using namespace std::chrono_literals;
 
 namespace {
@@ -139,6 +144,16 @@
     }
 }
 
+#if FLAG_BQ_SET_FRAME_RATE
+void BLASTBufferItemConsumer::onSetFrameRate(float frameRate, int8_t compatibility,
+                                             int8_t changeFrameRateStrategy) {
+    sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote();
+    if (bbq != nullptr) {
+        bbq->setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+    }
+}
+#endif
+
 void BLASTBufferItemConsumer::resizeFrameEventHistory(size_t newSize) {
     Mutex::Autolock lock(mMutex);
     mFrameEventHistory.resize(newSize);
@@ -890,6 +905,10 @@
 
     status_t setFrameRate(float frameRate, int8_t compatibility,
                           int8_t changeFrameRateStrategy) override {
+        if (flags::bq_setframerate()) {
+            return Surface::setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+        }
+
         std::lock_guard _lock{mMutex};
         if (mDestroyed) {
             return DEAD_OBJECT;
diff --git a/libs/gui/BufferQueue.cpp b/libs/gui/BufferQueue.cpp
index 66cad03..ab0f6d2 100644
--- a/libs/gui/BufferQueue.cpp
+++ b/libs/gui/BufferQueue.cpp
@@ -22,6 +22,7 @@
 #include <gui/BufferQueueConsumer.h>
 #include <gui/BufferQueueCore.h>
 #include <gui/BufferQueueProducer.h>
+#include <gui/Flags.h>
 
 namespace android {
 
@@ -98,6 +99,16 @@
     }
 }
 
+#if FLAG_BQ_SET_FRAME_RATE
+void BufferQueue::ProxyConsumerListener::onSetFrameRate(float frameRate, int8_t compatibility,
+                                                        int8_t changeFrameRateStrategy) {
+    sp<ConsumerListener> listener(mConsumerListener.promote());
+    if (listener != nullptr) {
+        listener->onSetFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+    }
+}
+#endif
+
 void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
         sp<IGraphicBufferConsumer>* outConsumer,
         bool consumerIsSurfaceFlinger) {
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index 920b83d..67dff6d 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -32,6 +32,8 @@
 #include <gui/BufferItem.h>
 #include <gui/BufferQueueCore.h>
 #include <gui/BufferQueueProducer.h>
+#include <gui/Flags.h>
+#include <gui/FrameRateUtils.h>
 #include <gui/GLConsumer.h>
 #include <gui/IConsumerListener.h>
 #include <gui/IProducerListener.h>
@@ -1751,4 +1753,27 @@
     return NO_ERROR;
 }
 
+#if FLAG_BQ_SET_FRAME_RATE
+status_t BufferQueueProducer::setFrameRate(float frameRate, int8_t compatibility,
+                                           int8_t changeFrameRateStrategy) {
+    ATRACE_CALL();
+    BQ_LOGV("setFrameRate: %.2f", frameRate);
+
+    if (!ValidateFrameRate(frameRate, compatibility, changeFrameRateStrategy,
+                           "BufferQueueProducer::setFrameRate")) {
+        return BAD_VALUE;
+    }
+
+    sp<IConsumerListener> listener;
+    {
+        std::lock_guard<std::mutex> lock(mCore->mMutex);
+        listener = mCore->mConsumerListener;
+    }
+    if (listener != nullptr) {
+        listener->onSetFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+    }
+    return NO_ERROR;
+}
+#endif
+
 } // namespace android
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index 46fb068..93df124 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -324,6 +324,12 @@
           to_string(displayId).c_str(), toString(connected));
 }
 
+void Choreographer::dispatchHotplugConnectionError(nsecs_t, int32_t connectionError) {
+    ALOGV("choreographer %p ~ received hotplug connection error event (connectionError=%d), "
+          "ignoring.",
+          this, connectionError);
+}
+
 void Choreographer::dispatchModeChanged(nsecs_t, PhysicalDisplayId, int32_t, nsecs_t) {
     LOG_ALWAYS_FATAL("dispatchModeChanged was called but was never registered");
 }
@@ -394,4 +400,4 @@
     return iter->second;
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp
index 8a88377..5dd058c 100644
--- a/libs/gui/DisplayEventDispatcher.cpp
+++ b/libs/gui/DisplayEventDispatcher.cpp
@@ -173,7 +173,13 @@
                     *outVsyncEventData = ev.vsync.vsyncData;
                     break;
                 case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
-                    dispatchHotplug(ev.header.timestamp, ev.header.displayId, ev.hotplug.connected);
+                    if (ev.hotplug.connectionError == 0) {
+                        dispatchHotplug(ev.header.timestamp, ev.header.displayId,
+                                        ev.hotplug.connected);
+                    } else {
+                        dispatchHotplugConnectionError(ev.header.timestamp,
+                                                       ev.hotplug.connectionError);
+                    }
                     break;
                 case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE:
                     dispatchModeChanged(ev.header.timestamp, ev.header.displayId,
diff --git a/libs/gui/FrameRateUtils.cpp b/libs/gui/FrameRateUtils.cpp
new file mode 100644
index 0000000..6993bfa
--- /dev/null
+++ b/libs/gui/FrameRateUtils.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2023 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 <gui/Flags.h>
+#include <gui/FrameRateUtils.h>
+#include <system/window.h>
+#include <utils/Log.h>
+
+#include <cmath>
+
+namespace android {
+// Returns true if the frameRate is valid.
+//
+// @param frameRate the frame rate in Hz
+// @param compatibility a ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_*
+// @param changeFrameRateStrategy a ANATIVEWINDOW_CHANGE_FRAME_RATE_*
+// @param functionName calling function or nullptr. Used for logging
+// @param privileged whether caller has unscoped surfaceflinger access
+bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy,
+                       const char* inFunctionName, bool privileged) {
+    const char* functionName = inFunctionName != nullptr ? inFunctionName : "call";
+    int floatClassification = std::fpclassify(frameRate);
+    if (frameRate < 0 || floatClassification == FP_INFINITE || floatClassification == FP_NAN) {
+        ALOGE("%s failed - invalid frame rate %f", functionName, frameRate);
+        return false;
+    }
+
+    if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT &&
+        compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE &&
+        (!privileged ||
+         (compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT &&
+          compatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE))) {
+        ALOGE("%s failed - invalid compatibility value %d privileged: %s", functionName,
+              compatibility, privileged ? "yes" : "no");
+        return false;
+    }
+
+    if (__builtin_available(android 31, *)) {
+        if (changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS &&
+            changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS) {
+            ALOGE("%s failed - invalid change frame rate strategy value %d", functionName,
+                  changeFrameRateStrategy);
+            if (FLAG_BQ_SET_FRAME_RATE) {
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+} // namespace android
diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index 918ff2d..d0c09e4 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -27,11 +27,12 @@
 #include <binder/Parcel.h>
 #include <binder/IInterface.h>
 
-#include <gui/bufferqueue/1.0/H2BGraphicBufferProducer.h>
-#include <gui/bufferqueue/2.0/H2BGraphicBufferProducer.h>
 #include <gui/BufferQueueDefs.h>
+#include <gui/Flags.h>
 #include <gui/IGraphicBufferProducer.h>
 #include <gui/IProducerListener.h>
+#include <gui/bufferqueue/1.0/H2BGraphicBufferProducer.h>
+#include <gui/bufferqueue/2.0/H2BGraphicBufferProducer.h>
 
 namespace android {
 // ----------------------------------------------------------------------------
@@ -78,6 +79,7 @@
     CANCEL_BUFFERS,
     QUERY_MULTIPLE,
     GET_LAST_QUEUED_BUFFER2,
+    SET_FRAME_RATE,
 };
 
 class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>
@@ -761,6 +763,21 @@
         }
         return result;
     }
+#if FLAG_BQ_SET_FRAME_RATE
+    virtual status_t setFrameRate(float frameRate, int8_t compatibility,
+                                  int8_t changeFrameRateStrategy) override {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+        data.writeFloat(frameRate);
+        data.writeInt32(compatibility);
+        data.writeInt32(changeFrameRateStrategy);
+        status_t result = remote()->transact(SET_FRAME_RATE, data, &reply);
+        if (result == NO_ERROR) {
+            result = reply.readInt32();
+        }
+        return result;
+    }
+#endif
 };
 
 // Out-of-line virtual method definition to trigger vtable emission in this
@@ -956,6 +973,14 @@
     return INVALID_OPERATION;
 }
 
+#if FLAG_BQ_SET_FRAME_RATE
+status_t IGraphicBufferProducer::setFrameRate(float /*frameRate*/, int8_t /*compatibility*/,
+                                              int8_t /*changeFrameRateStrategy*/) {
+    // No-op for IGBP other than BufferQueue.
+    return INVALID_OPERATION;
+}
+#endif
+
 status_t IGraphicBufferProducer::exportToParcel(Parcel* parcel) {
     status_t res = OK;
     res = parcel->writeUint32(USE_BUFFER_QUEUE);
@@ -1497,6 +1522,17 @@
             reply->writeInt32(result);
             return NO_ERROR;
         }
+#if FLAG_BQ_SET_FRAME_RATE
+        case SET_FRAME_RATE: {
+            CHECK_INTERFACE(IGraphicBuffer, data, reply);
+            float frameRate = data.readFloat();
+            int8_t compatibility = data.readInt32();
+            int8_t changeFrameRateStrategy = data.readInt32();
+            status_t result = setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+            reply->writeInt32(result);
+            return NO_ERROR;
+        }
+#endif
     }
     return BBinder::onTransact(code, data, reply, flags);
 }
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index e1afb52..613721e 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -22,6 +22,7 @@
 #include <android/gui/ISurfaceComposerClient.h>
 #include <android/native_window.h>
 #include <binder/Parcel.h>
+#include <gui/FrameRateUtils.h>
 #include <gui/IGraphicBufferProducer.h>
 #include <gui/LayerState.h>
 #include <gui/SurfaceControl.h>
@@ -84,6 +85,7 @@
         changeFrameRateStrategy(ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS),
         defaultFrameRateCompatibility(ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT),
         frameRateCategory(ANATIVEWINDOW_FRAME_RATE_CATEGORY_DEFAULT),
+        frameRateSelectionStrategy(ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF),
         fixedTransformHint(ui::Transform::ROT_INVALID),
         autoRefresh(false),
         isTrustedOverlay(false),
@@ -160,6 +162,7 @@
     SAFE_PARCEL(output.writeByte, changeFrameRateStrategy);
     SAFE_PARCEL(output.writeByte, defaultFrameRateCompatibility);
     SAFE_PARCEL(output.writeByte, frameRateCategory);
+    SAFE_PARCEL(output.writeByte, frameRateSelectionStrategy);
     SAFE_PARCEL(output.writeUint32, fixedTransformHint);
     SAFE_PARCEL(output.writeBool, autoRefresh);
     SAFE_PARCEL(output.writeBool, dimmingEnabled);
@@ -293,6 +296,7 @@
     SAFE_PARCEL(input.readByte, &changeFrameRateStrategy);
     SAFE_PARCEL(input.readByte, &defaultFrameRateCompatibility);
     SAFE_PARCEL(input.readByte, &frameRateCategory);
+    SAFE_PARCEL(input.readByte, &frameRateSelectionStrategy);
     SAFE_PARCEL(input.readUint32, &tmpUint32);
     fixedTransformHint = static_cast<ui::Transform::RotationFlags>(tmpUint32);
     SAFE_PARCEL(input.readBool, &autoRefresh);
@@ -666,6 +670,10 @@
         what |= eFrameRateCategoryChanged;
         frameRateCategory = other.frameRateCategory;
     }
+    if (other.what & eFrameRateSelectionStrategyChanged) {
+        what |= eFrameRateSelectionStrategyChanged;
+        frameRateSelectionStrategy = other.frameRateSelectionStrategy;
+    }
     if (other.what & eFixedTransformHintChanged) {
         what |= eFixedTransformHintChanged;
         fixedTransformHint = other.fixedTransformHint;
@@ -777,6 +785,7 @@
     CHECK_DIFF3(diff, eFrameRateChanged, other, frameRate, frameRateCompatibility,
                 changeFrameRateStrategy);
     CHECK_DIFF(diff, eFrameRateCategoryChanged, other, frameRateCategory);
+    CHECK_DIFF(diff, eFrameRateSelectionStrategyChanged, other, frameRateSelectionStrategy);
     CHECK_DIFF(diff, eFixedTransformHintChanged, other, fixedTransformHint);
     CHECK_DIFF(diff, eAutoRefreshChanged, other, autoRefresh);
     CHECK_DIFF(diff, eTrustedOverlayChanged, other, isTrustedOverlay);
@@ -863,34 +872,6 @@
     return NO_ERROR;
 }
 
-bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy,
-                       const char* inFunctionName, bool privileged) {
-    const char* functionName = inFunctionName != nullptr ? inFunctionName : "call";
-    int floatClassification = std::fpclassify(frameRate);
-    if (frameRate < 0 || floatClassification == FP_INFINITE || floatClassification == FP_NAN) {
-        ALOGE("%s failed - invalid frame rate %f", functionName, frameRate);
-        return false;
-    }
-
-    if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT &&
-        compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE &&
-        (!privileged ||
-         (compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT &&
-          compatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE))) {
-        ALOGE("%s failed - invalid compatibility value %d privileged: %s", functionName,
-              compatibility, privileged ? "yes" : "no");
-        return false;
-    }
-
-    if (changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS &&
-        changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS) {
-        ALOGE("%s failed - invalid change frame rate strategy value %d", functionName,
-              changeFrameRateStrategy);
-    }
-
-    return true;
-}
-
 // ----------------------------------------------------------------------------
 
 namespace gui {
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 53a2f64..a87f053 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -43,6 +43,7 @@
 
 #include <gui/AidlStatusUtil.h>
 #include <gui/BufferItem.h>
+#include <gui/Flags.h>
 #include <gui/IProducerListener.h>
 
 #include <gui/ISurfaceComposer.h>
@@ -50,8 +51,11 @@
 #include <private/gui/ComposerService.h>
 #include <private/gui/ComposerServiceAIDL.h>
 
+#include <com_android_graphics_libgui_flags.h>
+
 namespace android {
 
+using namespace com::android::graphics::libgui;
 using gui::aidl_utils::statusTFromBinderStatus;
 using ui::Dataspace;
 
@@ -2565,8 +2569,22 @@
     mSurfaceListener->onBuffersDiscarded(discardedBufs);
 }
 
-[[deprecated]] status_t Surface::setFrameRate(float /*frameRate*/, int8_t /*compatibility*/,
-                                              int8_t /*changeFrameRateStrategy*/) {
+status_t Surface::setFrameRate(float frameRate, int8_t compatibility,
+                               int8_t changeFrameRateStrategy) {
+#if FLAG_BQ_SET_FRAME_RATE
+    if (flags::bq_setframerate()) {
+        status_t err = mGraphicBufferProducer->setFrameRate(frameRate, compatibility,
+                                                            changeFrameRateStrategy);
+        ALOGE_IF(err, "IGraphicBufferProducer::setFrameRate(%.2f) returned %s", frameRate,
+                 strerror(-err));
+        return err;
+    }
+#else
+    static_cast<void>(frameRate);
+    static_cast<void>(compatibility);
+    static_cast<void>(changeFrameRateStrategy);
+#endif
+
     ALOGI("Surface::setFrameRate is deprecated, setFrameRate hint is dropped as destination is not "
           "SurfaceFlinger");
     // ISurfaceComposer no longer supports setFrameRate, we will return NO_ERROR when the api is
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 4db960e..038764b 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -26,6 +26,7 @@
 #include <android/gui/IWindowInfosListener.h>
 #include <android/gui/TrustedPresentationThresholds.h>
 #include <android/os/IInputConstants.h>
+#include <gui/FrameRateUtils.h>
 #include <gui/TraceUtils.h>
 #include <utils/Errors.h>
 #include <utils/Log.h>
@@ -2104,6 +2105,19 @@
     return *this;
 }
 
+SurfaceComposerClient::Transaction&
+SurfaceComposerClient::Transaction::setFrameRateSelectionStrategy(const sp<SurfaceControl>& sc,
+                                                                  int8_t strategy) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+    s->what |= layer_state_t::eFrameRateSelectionStrategyChanged;
+    s->frameRateSelectionStrategy = strategy;
+    return *this;
+}
+
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFixedTransformHint(
         const sp<SurfaceControl>& sc, int32_t fixedTransformHint) {
     layer_state_t* s = getLayerState(sc);
diff --git a/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp b/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp
index 6e4f074..0d2a52b 100644
--- a/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp
+++ b/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp
@@ -62,7 +62,10 @@
 
         }
         case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG: {
-            event.hotplug = DisplayEventReceiver::Event::Hotplug{fdp->ConsumeBool() /*connected*/};
+            event.hotplug =
+                    DisplayEventReceiver::Event::Hotplug{fdp->ConsumeBool() /*connected*/,
+                                                         fdp->ConsumeIntegral<
+                                                                 int32_t>() /*connectionError*/};
             break;
         }
         case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE: {
diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h
index 177d5f8..bdf8856 100644
--- a/libs/gui/fuzzer/libgui_fuzzer_utils.h
+++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h
@@ -197,6 +197,7 @@
 
     MOCK_METHOD4(dispatchVsync, void(nsecs_t, PhysicalDisplayId, uint32_t, VsyncEventData));
     MOCK_METHOD3(dispatchHotplug, void(nsecs_t, PhysicalDisplayId, bool));
+    MOCK_METHOD2(dispatchHotplugConnectionError, void(nsecs_t, int32_t));
     MOCK_METHOD4(dispatchModeChanged, void(nsecs_t, PhysicalDisplayId, int32_t, nsecs_t));
     MOCK_METHOD2(dispatchNullEvent, void(nsecs_t, PhysicalDisplayId));
     MOCK_METHOD3(dispatchFrameRateOverrides,
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index a49a859..02d7c4d 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -17,9 +17,10 @@
 #ifndef ANDROID_GUI_BLAST_BUFFER_QUEUE_H
 #define ANDROID_GUI_BLAST_BUFFER_QUEUE_H
 
-#include <gui/IGraphicBufferProducer.h>
-#include <gui/BufferItemConsumer.h>
 #include <gui/BufferItem.h>
+#include <gui/BufferItemConsumer.h>
+#include <gui/Flags.h>
+#include <gui/IGraphicBufferProducer.h>
 #include <gui/SurfaceComposerClient.h>
 
 #include <utils/Condition.h>
@@ -58,6 +59,10 @@
 
 protected:
     void onSidebandStreamChanged() override EXCLUDES(mMutex);
+#if FLAG_BQ_SET_FRAME_RATE
+    void onSetFrameRate(float frameRate, int8_t compatibility,
+                        int8_t changeFrameRateStrategy) override;
+#endif
 
 private:
     const wp<BLASTBufferQueue> mBLASTBufferQueue;
diff --git a/libs/gui/include/gui/BufferQueue.h b/libs/gui/include/gui/BufferQueue.h
index 690587f..2756277 100644
--- a/libs/gui/include/gui/BufferQueue.h
+++ b/libs/gui/include/gui/BufferQueue.h
@@ -19,9 +19,10 @@
 
 #include <gui/BufferItem.h>
 #include <gui/BufferQueueDefs.h>
+#include <gui/Flags.h>
+#include <gui/IConsumerListener.h>
 #include <gui/IGraphicBufferConsumer.h>
 #include <gui/IGraphicBufferProducer.h>
-#include <gui/IConsumerListener.h>
 
 namespace android {
 
@@ -69,6 +70,10 @@
         void addAndGetFrameTimestamps(
                 const NewFrameEventsEntry* newTimestamps,
                 FrameEventHistoryDelta* outDelta) override;
+#if FLAG_BQ_SET_FRAME_RATE
+        void onSetFrameRate(float frameRate, int8_t compatibility,
+                            int8_t changeFrameRateStrategy) override;
+#endif
     private:
         // mConsumerListener is a weak reference to the IConsumerListener.  This is
         // the raison d'etre of ProxyConsumerListener.
diff --git a/libs/gui/include/gui/BufferQueueProducer.h b/libs/gui/include/gui/BufferQueueProducer.h
index 1d13dab..38805d0 100644
--- a/libs/gui/include/gui/BufferQueueProducer.h
+++ b/libs/gui/include/gui/BufferQueueProducer.h
@@ -18,6 +18,7 @@
 #define ANDROID_GUI_BUFFERQUEUEPRODUCER_H
 
 #include <gui/BufferQueueDefs.h>
+#include <gui/Flags.h>
 #include <gui/IGraphicBufferProducer.h>
 
 namespace android {
@@ -201,6 +202,11 @@
 
     // See IGraphicBufferProducer::setAutoPrerotation
     virtual status_t setAutoPrerotation(bool autoPrerotation);
+#if FLAG_BQ_SET_FRAME_RATE
+    // See IGraphicBufferProducer::setFrameRate
+    status_t setFrameRate(float frameRate, int8_t compatibility,
+                          int8_t changeFrameRateStrategy) override;
+#endif
 
 protected:
     // see IGraphicsBufferProducer::setMaxDequeuedBufferCount, but with the ability to retrieve the
diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h
index 1df9b11..9fef512 100644
--- a/libs/gui/include/gui/Choreographer.h
+++ b/libs/gui/include/gui/Choreographer.h
@@ -110,6 +110,7 @@
     void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
                        VsyncEventData vsyncEventData) override;
     void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
+    void dispatchHotplugConnectionError(nsecs_t timestamp, int32_t connectionError) override;
     void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
                              nsecs_t vsyncPeriod) override;
     void dispatchNullEvent(nsecs_t, PhysicalDisplayId) override;
@@ -137,4 +138,4 @@
     static constexpr size_t kMaxStartTimes = 250;
 };
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h
index 140efa6..fe2dd20 100644
--- a/libs/gui/include/gui/DisplayEventDispatcher.h
+++ b/libs/gui/include/gui/DisplayEventDispatcher.h
@@ -53,6 +53,9 @@
                                VsyncEventData vsyncEventData) = 0;
     virtual void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId,
                                  bool connected) = 0;
+
+    virtual void dispatchHotplugConnectionError(nsecs_t timestamp, int32_t connectionError) = 0;
+
     virtual void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
                                      nsecs_t vsyncPeriod) = 0;
     // AChoreographer-specific hook for processing null-events so that looper
diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h
index 7fd6c35..79582ce 100644
--- a/libs/gui/include/gui/DisplayEventReceiver.h
+++ b/libs/gui/include/gui/DisplayEventReceiver.h
@@ -88,6 +88,7 @@
 
         struct Hotplug {
             bool connected;
+            int32_t connectionError __attribute__((aligned(4)));
         };
 
         struct ModeChange {
diff --git a/services/inputflinger/host/main.cpp b/libs/gui/include/gui/Flags.h
similarity index 64%
rename from services/inputflinger/host/main.cpp
rename to libs/gui/include/gui/Flags.h
index 0a517cc..a2cff56 100644
--- a/services/inputflinger/host/main.cpp
+++ b/libs/gui/include/gui/Flags.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright 2023 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.
@@ -14,13 +14,9 @@
  * limitations under the License.
  */
 
-#include <binder/BinderService.h>
-#include "InputFlinger.h"
+#pragma once
 
-using namespace android;
-
-int main(int, char**) {
-    ProcessState::self()->setThreadPoolMaxThreadCount(4);
-    BinderService<InputFlinger>::publishAndJoinThreadPool(true);
-    return 0;
-}
+// TODO(281695725): replace this with build time flags, whenever they are available
+#ifndef FLAG_BQ_SET_FRAME_RATE
+#define FLAG_BQ_SET_FRAME_RATE false
+#endif
\ No newline at end of file
diff --git a/services/inputflinger/host/main.cpp b/libs/gui/include/gui/FrameRateUtils.h
similarity index 64%
copy from services/inputflinger/host/main.cpp
copy to libs/gui/include/gui/FrameRateUtils.h
index 0a517cc..16896ef 100644
--- a/services/inputflinger/host/main.cpp
+++ b/libs/gui/include/gui/FrameRateUtils.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright 2023 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.
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-#include <binder/BinderService.h>
-#include "InputFlinger.h"
+#pragma once
 
-using namespace android;
+#include <stdint.h>
 
-int main(int, char**) {
-    ProcessState::self()->setThreadPoolMaxThreadCount(4);
-    BinderService<InputFlinger>::publishAndJoinThreadPool(true);
-    return 0;
-}
+namespace android {
+
+bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy,
+                       const char* inFunctionName, bool privileged = false);
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/gui/include/gui/IConsumerListener.h b/libs/gui/include/gui/IConsumerListener.h
index 0ab2399..e183bf2 100644
--- a/libs/gui/include/gui/IConsumerListener.h
+++ b/libs/gui/include/gui/IConsumerListener.h
@@ -19,6 +19,8 @@
 #include <binder/IInterface.h>
 #include <binder/SafeInterface.h>
 
+#include <gui/Flags.h>
+
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
 
@@ -90,6 +92,12 @@
     // WARNING: This method can only be called when the BufferQueue is in the consumer's process.
     virtual void addAndGetFrameTimestamps(const NewFrameEventsEntry* /*newTimestamps*/,
                                           FrameEventHistoryDelta* /*outDelta*/) {}
+
+#if FLAG_BQ_SET_FRAME_RATE
+    // Notifies the consumer of a setFrameRate call from the producer side.
+    virtual void onSetFrameRate(float /*frameRate*/, int8_t /*compatibility*/,
+                                int8_t /*changeFrameRateStrategy*/) {}
+#endif
 };
 
 #ifndef NO_BINDER
diff --git a/libs/gui/include/gui/IGraphicBufferProducer.h b/libs/gui/include/gui/IGraphicBufferProducer.h
index 98df834..3562906 100644
--- a/libs/gui/include/gui/IGraphicBufferProducer.h
+++ b/libs/gui/include/gui/IGraphicBufferProducer.h
@@ -31,6 +31,7 @@
 #include <ui/Rect.h>
 #include <ui/Region.h>
 
+#include <gui/Flags.h>
 #include <gui/FrameTimestamps.h>
 #include <gui/HdrMetadata.h>
 
@@ -676,6 +677,12 @@
     // the width and height used for dequeueBuffer will be additionally swapped.
     virtual status_t setAutoPrerotation(bool autoPrerotation);
 
+#if FLAG_BQ_SET_FRAME_RATE
+    // Sets the apps intended frame rate.
+    virtual status_t setFrameRate(float frameRate, int8_t compatibility,
+                                  int8_t changeFrameRateStrategy);
+#endif
+
     struct RequestBufferOutput : public Flattenable<RequestBufferOutput> {
         RequestBufferOutput() = default;
 
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 8c36058..4371007 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -197,7 +197,7 @@
         eInputInfoChanged = 0x40000000,
         eCornerRadiusChanged = 0x80000000,
         eDestinationFrameChanged = 0x1'00000000,
-        /* unused = 0x2'00000000, */
+        eFrameRateSelectionStrategyChanged = 0x2'00000000,
         eBackgroundColorChanged = 0x4'00000000,
         eMetadataChanged = 0x8'00000000,
         eColorSpaceAgnosticChanged = 0x10'00000000,
@@ -268,6 +268,7 @@
             layer_state_t::eColorTransformChanged | layer_state_t::eCornerRadiusChanged |
             layer_state_t::eFlagsChanged | layer_state_t::eTrustedOverlayChanged |
             layer_state_t::eFrameRateChanged | layer_state_t::eFrameRateCategoryChanged |
+            layer_state_t::eFrameRateSelectionStrategyChanged |
             layer_state_t::eFrameRateSelectionPriority | layer_state_t::eFixedTransformHintChanged;
 
     // Changes affecting data sent to input.
@@ -361,6 +362,9 @@
     // Frame rate category to suggest what frame rate range a surface should run.
     int8_t frameRateCategory;
 
+    // Strategy of the layer for frame rate selection.
+    int8_t frameRateSelectionStrategy;
+
     // Set by window manager indicating the layer and all its children are
     // in a different orientation than the display. The hint suggests that
     // the graphic producers should receive a transform hint as if the
@@ -476,16 +480,6 @@
     return compare_type(lhs.token, rhs.token);
 }
 
-// Returns true if the frameRate is valid.
-//
-// @param frameRate the frame rate in Hz
-// @param compatibility a ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_*
-// @param changeFrameRateStrategy a ANATIVEWINDOW_CHANGE_FRAME_RATE_*
-// @param functionName calling function or nullptr. Used for logging
-// @param privileged whether caller has unscoped surfaceflinger access
-bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy,
-                       const char* functionName, bool privileged = false);
-
 }; // namespace android
 
 #endif // ANDROID_SF_LAYER_STATE_H
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 6fef5d2..42e3c16 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -687,6 +687,8 @@
 
         Transaction& setFrameRateCategory(const sp<SurfaceControl>& sc, int8_t category);
 
+        Transaction& setFrameRateSelectionStrategy(const sp<SurfaceControl>& sc, int8_t strategy);
+
         // Set by window manager indicating the layer and all its children are
         // in a different orientation than the display. The hint suggests that
         // the graphic producers should receive a transform hint as if the
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index 462ce6e..38c0eed 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -15,9 +15,13 @@
     name: "libgui_test",
     test_suites: ["device-tests"],
 
-    cflags: [
+    defaults: ["libgui-defaults"],
+
+    cppflags: [
         "-Wall",
         "-Werror",
+        "-Wno-extra",
+        "-DFLAG_BQ_SET_FRAME_RATE=true",
     ],
 
     srcs: [
@@ -28,6 +32,7 @@
         "CompositorTiming_test.cpp",
         "CpuConsumer_test.cpp",
         "EndToEndNativeInputTest.cpp",
+        "FrameRateUtilsTest.cpp",
         "DisplayInfo_test.cpp",
         "DisplayedContentSampling_test.cpp",
         "FillBuffer.cpp",
@@ -53,19 +58,12 @@
         "android.hardware.configstore@1.0",
         "android.hardware.configstore-utils",
         "libSurfaceFlingerProp",
-        "libbase",
-        "liblog",
-        "libEGL",
         "libGLESv1_CM",
-        "libGLESv2",
-        "libbinder",
-        "libcutils",
-        "libgui",
-        "libhidlbase",
         "libinput",
-        "libui",
-        "libutils",
-        "libnativewindow",
+    ],
+
+    static_libs: [
+        "libgmock",
     ],
 
     header_libs: ["libsurfaceflinger_headers"],
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index 0168877..17aa5f1 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -37,14 +37,18 @@
 
 #include <system/window.h>
 
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
 #include <future>
 #include <thread>
 
+#include <com_android_graphics_libgui_flags.h>
+
 using namespace std::chrono_literals;
 
 namespace android {
+using namespace com::android::graphics::libgui;
 
 class BufferQueueTest : public ::testing::Test {
 
@@ -1261,6 +1265,31 @@
     ASSERT_EQ(NO_INIT, mProducer->disconnect(NATIVE_WINDOW_API_CPU));
 }
 
+TEST_F(BufferQueueTest, TestBqSetFrameRateFlagBuildTimeIsSet) {
+    if (flags::bq_setframerate()) {
+        ASSERT_EQ(true, FLAG_BQ_SET_FRAME_RATE);
+    }
+}
+
+struct BufferItemConsumerSetFrameRateListener : public BufferItemConsumer {
+    BufferItemConsumerSetFrameRateListener(const sp<IGraphicBufferConsumer>& consumer)
+          : BufferItemConsumer(consumer, GRALLOC_USAGE_SW_READ_OFTEN, 1) {}
+
+    MOCK_METHOD(void, onSetFrameRate, (float, int8_t, int8_t), (override));
+};
+
+TEST_F(BufferQueueTest, TestSetFrameRate) {
+    sp<IGraphicBufferProducer> producer;
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&producer, &consumer);
+
+    sp<BufferItemConsumerSetFrameRateListener> bufferConsumer =
+            sp<BufferItemConsumerSetFrameRateListener>::make(consumer);
+
+    EXPECT_CALL(*bufferConsumer, onSetFrameRate(12.34f, 1, 0)).Times(1);
+    producer->setFrameRate(12.34f, 1, 0);
+}
+
 class Latch {
 public:
     explicit Latch(int expected) : mExpected(expected) {}
diff --git a/libs/gui/tests/DisplayEventStructLayout_test.cpp b/libs/gui/tests/DisplayEventStructLayout_test.cpp
index 3949d70..29eeaa8 100644
--- a/libs/gui/tests/DisplayEventStructLayout_test.cpp
+++ b/libs/gui/tests/DisplayEventStructLayout_test.cpp
@@ -59,6 +59,7 @@
                  lastFrameTimelineOffset + 16);
 
     CHECK_OFFSET(DisplayEventReceiver::Event::Hotplug, connected, 0);
+    CHECK_OFFSET(DisplayEventReceiver::Event::Hotplug, connectionError, 4);
 
     CHECK_OFFSET(DisplayEventReceiver::Event::ModeChange, modeId, 0);
     CHECK_OFFSET(DisplayEventReceiver::Event::ModeChange, vsyncPeriod, 8);
diff --git a/libs/gui/tests/FrameRateUtilsTest.cpp b/libs/gui/tests/FrameRateUtilsTest.cpp
new file mode 100644
index 0000000..5fe22b0
--- /dev/null
+++ b/libs/gui/tests/FrameRateUtilsTest.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2018 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 <gtest/gtest.h>
+
+#include <gui/FrameRateUtils.h>
+#include <inttypes.h>
+#include <system/window.h>
+
+#include <com_android_graphics_libgui_flags.h>
+
+namespace android {
+using namespace com::android::graphics::libgui;
+
+TEST(FrameRateUtilsTest, ValidateFrameRate) {
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS, ""));
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+    // Privileged APIs.
+    EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
+                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_FALSE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
+                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+    constexpr bool kPrivileged = true;
+    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "",
+                                  kPrivileged));
+    EXPECT_TRUE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
+                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "",
+                                  kPrivileged));
+
+    // Invalid frame rate.
+    EXPECT_FALSE(ValidateFrameRate(-1, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_FALSE(ValidateFrameRate(1.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_FALSE(ValidateFrameRate(0.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+    // Invalid compatibility.
+    EXPECT_FALSE(
+            ValidateFrameRate(60.0f, -1, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+    EXPECT_FALSE(ValidateFrameRate(60.0f, 2, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+    // Invalid change frame rate strategy.
+    if (flags::bq_setframerate()) {
+        EXPECT_FALSE(
+                ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, -1, ""));
+        EXPECT_FALSE(
+                ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, 2, ""));
+    }
+}
+
+} // namespace android
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 1f14396..1600013 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -476,8 +476,6 @@
 }
 
 status_t InputChannel::receiveMessage(InputMessage* msg) {
-    ATRACE_NAME_IF(ATRACE_ENABLED(),
-                   StringPrintf("receiveMessage(inputChannel=%s)", mName.c_str()));
     ssize_t nRead;
     do {
         nRead = ::recv(getFd(), msg, sizeof(InputMessage), MSG_DONTWAIT);
diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index c8d1da7..412931b 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -205,6 +205,13 @@
         coords.setAxisValue(AMOTION_EVENT_AXIS_X, predictedPoint.x);
         coords.setAxisValue(AMOTION_EVENT_AXIS_Y, predictedPoint.y);
         coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, predictedPressure[i]);
+        // Copy forward tilt and orientation from the last event until they are predicted
+        // (b/291789258).
+        coords.setAxisValue(AMOTION_EVENT_AXIS_TILT,
+                            event.getAxisValue(AMOTION_EVENT_AXIS_TILT, 0));
+        coords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION,
+                            event.getRawPointerCoords(0)->getAxisValue(
+                                    AMOTION_EVENT_AXIS_ORIENTATION));
 
         predictionTime += mModel->config().predictionInterval;
         if (i == 0) {
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index 2af5a4a..a0563f9 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -20,3 +20,10 @@
   description: "Set to true to enable PointerChoreographer: the new pipeline for showing pointer icons"
   bug: "293587049"
 }
+
+flag {
+  name: "enable_gestures_library_timer_provider"
+  namespace: "input"
+  description: "Set to true to enable timer support for the touchpad Gestures library"
+  bug: "297192727"
+}
diff --git a/libs/input/tests/InputDevice_test.cpp b/libs/input/tests/InputDevice_test.cpp
index ee961f0..a0ec6ad 100644
--- a/libs/input/tests/InputDevice_test.cpp
+++ b/libs/input/tests/InputDevice_test.cpp
@@ -20,6 +20,7 @@
 #include <input/InputDevice.h>
 #include <input/KeyLayoutMap.h>
 #include <input/Keyboard.h>
+#include <linux/uinput.h>
 #include "android-base/file.h"
 
 namespace android {
@@ -97,7 +98,7 @@
     ASSERT_EQ(*map, *mKeyMap.keyCharacterMap);
 }
 
-TEST_F(InputDeviceKeyMapTest, keyCharacteMapApplyMultipleOverlaysTest) {
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapApplyMultipleOverlaysTest) {
     std::string frenchOverlayPath = base::GetExecutableDirectory() + "/data/french.kcm";
     std::string englishOverlayPath = base::GetExecutableDirectory() + "/data/english_us.kcm";
     std::string germanOverlayPath = base::GetExecutableDirectory() + "/data/german.kcm";
@@ -133,14 +134,33 @@
     ASSERT_EQ(*mKeyMap.keyCharacterMap, *frenchOverlaidKeyCharacterMap);
 }
 
-TEST_F(InputDeviceKeyMapTest, keyCharacteMapBadAxisLabel) {
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapApplyOverlayTest) {
+    std::string frenchOverlayPath = base::GetExecutableDirectory() + "/data/french.kcm";
+    base::Result<std::shared_ptr<KeyCharacterMap>> frenchOverlay =
+            KeyCharacterMap::load(frenchOverlayPath, KeyCharacterMap::Format::OVERLAY);
+    ASSERT_TRUE(frenchOverlay.ok()) << "Cannot load KeyCharacterMap at " << frenchOverlayPath;
+
+    // Apply the French overlay
+    mKeyMap.keyCharacterMap->combine(*frenchOverlay->get());
+
+    // Check if mapping for key_Q is correct
+    int32_t outKeyCode;
+    status_t mapKeyResult = mKeyMap.keyCharacterMap->mapKey(KEY_Q, /*usageCode=*/0, &outKeyCode);
+    ASSERT_EQ(mapKeyResult, OK) << "No mapping for KEY_Q for " << frenchOverlayPath;
+    ASSERT_EQ(outKeyCode, AKEYCODE_A);
+
+    mapKeyResult = mKeyMap.keyCharacterMap->mapKey(KEY_E, /*usageCode=*/0, &outKeyCode);
+    ASSERT_NE(mapKeyResult, OK) << "Mapping exists for KEY_E for " << frenchOverlayPath;
+}
+
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapBadAxisLabel) {
     std::string klPath = base::GetExecutableDirectory() + "/data/bad_axis_label.kl";
 
     base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath);
     ASSERT_FALSE(ret.ok()) << "Should not be able to load KeyLayout at " << klPath;
 }
 
-TEST_F(InputDeviceKeyMapTest, keyCharacteMapBadLedLabel) {
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapBadLedLabel) {
     std::string klPath = base::GetExecutableDirectory() + "/data/bad_led_label.kl";
 
     base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath);
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index e158f01..b068f48 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1096,6 +1096,26 @@
     ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH = 4
 };
 
+/*
+ * Frame rate selection strategy values that can be used in
+ * Transaction::setFrameRateSelectionStrategy.
+ */
+enum {
+    /**
+     * Default value. The layer uses its own frame rate specifications, assuming it has any
+     * specifications, instead of its parent's.
+     */
+    ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF = 0,
+
+    /**
+     * The layer's frame rate specifications will propagate to and override those of its descendant
+     * layers.
+     * The layer with this strategy has the ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF
+     * behavior for itself.
+     */
+    ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN = 1,
+};
+
 static inline int native_window_set_frame_rate(struct ANativeWindow* window, float frameRate,
                                         int8_t compatibility, int8_t changeFrameRateStrategy) {
     return window->perform(window, NATIVE_WINDOW_SET_FRAME_RATE, (double)frameRate,
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index e253ad5..cc1d12b 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -24,6 +24,7 @@
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
 #include <GrContextOptions.h>
+#include <GrTypes.h>
 #include <android-base/stringprintf.h>
 #include <gl/GrGLInterface.h>
 #include <gui/TraceUtils.h>
@@ -338,7 +339,8 @@
     } else {
         ATRACE_BEGIN("Submit(sync=false)");
     }
-    bool success = grContext->submit(requireSync);
+    bool success = grContext->submit(requireSync ? GrSyncCpu::kYes :
+                                                   GrSyncCpu::kNo);
     ATRACE_END();
     if (!success) {
         ALOGE("Failed to flush RenderEngine commands");
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 1f18b94..7ec98ea 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -22,6 +22,7 @@
 
 #include <GrBackendSemaphore.h>
 #include <GrContextOptions.h>
+#include <GrTypes.h>
 #include <SkBlendMode.h>
 #include <SkCanvas.h>
 #include <SkColor.h>
@@ -289,12 +290,12 @@
     }
 
     if (mGrContext) {
-        mGrContext->flushAndSubmit(true);
+        mGrContext->flushAndSubmit(GrSyncCpu::kYes);
         mGrContext->abandonContext();
     }
 
     if (mProtectedGrContext) {
-        mProtectedGrContext->flushAndSubmit(true);
+        mProtectedGrContext->flushAndSubmit(GrSyncCpu::kYes);
         mProtectedGrContext->abandonContext();
     }
 }
@@ -307,7 +308,7 @@
 
     // release any scratch resources before switching into a new mode
     if (getActiveGrContext()) {
-        getActiveGrContext()->purgeUnlockedResources(true);
+        getActiveGrContext()->purgeUnlockedResources(GrPurgeResourceOptions::kScratchResourcesOnly);
     }
 
     // Backend-specific way to switch to protected context
@@ -399,7 +400,10 @@
     // simply match the existing behavior for protected buffers.)  We also never cache any
     // buffers while in a protected context.
     const bool isProtectedBuffer = buffer->getUsage() & GRALLOC_USAGE_PROTECTED;
-    if (isProtectedBuffer || isProtected()) {
+    // Don't attempt to map buffers if we're not gpu sampleable. Callers shouldn't send a buffer
+    // over to RenderEngine.
+    const bool isGpuSampleable = buffer->getUsage() & GRALLOC_USAGE_HW_TEXTURE;
+    if (isProtectedBuffer || isProtected() || !isGpuSampleable) {
         return;
     }
     ATRACE_CALL();
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index 6ecc6ab..17f263d 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -681,7 +681,7 @@
         flushInfo.fFinishedContext = destroySemaphoreInfo;
     }
     GrSemaphoresSubmitted submitted = grContext->flush(flushInfo);
-    grContext->submit(false /* no cpu sync */);
+    grContext->submit(GrSyncCpu::kNo);
     int drawFenceFd = -1;
     if (semaphore != VK_NULL_HANDLE) {
         if (GrSemaphoresSubmitted::kYes == submitted) {
diff --git a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
index 30149c1..b86ce5f4 100644
--- a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
@@ -136,7 +136,7 @@
     size_t mHeight;
 
     // Position of EXIF package, default value is -1 which means no EXIF package appears.
-    size_t mExifPos;
+    ssize_t mExifPos = -1;
 };
 } /* namespace android::ultrahdr  */
 
diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp
index 74760d9..3d70fce 100644
--- a/libs/ultrahdr/jpegr.cpp
+++ b/libs/ultrahdr/jpegr.cpp
@@ -85,13 +85,11 @@
                                 jr_compressed_ptr pSource,
                                 size_t exif_pos,
                                 size_t exif_size) {
-  memcpy(pDest, pSource, sizeof(jpegr_compressed_struct));
-
   const size_t exif_offset = 4; //exif_pos has 4 bytes offset to the FF sign
   pDest->length = pSource->length - exif_size - exif_offset;
   pDest->data = new uint8_t[pDest->length];
-  std::unique_ptr<uint8_t[]> dest_data;
-  dest_data.reset(reinterpret_cast<uint8_t*>(pDest->data));
+  pDest->maxLength = pDest->length;
+  pDest->colorGamut = pSource->colorGamut;
   memcpy(pDest->data, pSource->data, exif_pos - exif_offset);
   memcpy((uint8_t*)pDest->data + exif_pos - exif_offset,
          (uint8_t*)pSource->data + exif_pos + exif_size,
@@ -1262,13 +1260,13 @@
   if (!decoder.extractEXIF(primary_jpg_image_ptr->data, primary_jpg_image_ptr->length)) {
     return ERROR_JPEGR_DECODE_ERROR;
   }
-  jpegr_exif_struct exif_from_jpg;
-  exif_from_jpg.data = nullptr;
-  exif_from_jpg.length = 0;
-  jpegr_compressed_struct new_jpg_image;
-  new_jpg_image.data = nullptr;
-  new_jpg_image.length = 0;
-  if (decoder.getEXIFPos() != 0) {
+  jpegr_exif_struct exif_from_jpg = {.data = nullptr, .length = 0};
+  jpegr_compressed_struct new_jpg_image = {.data = nullptr,
+                                           .length = 0,
+                                           .maxLength = 0,
+                                           .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
+  std::unique_ptr<uint8_t[]> dest_data;
+  if (decoder.getEXIFPos() >= 0) {
     if (pExif != nullptr) {
       ALOGE("received EXIF from outside while the primary image already contains EXIF");
       return ERROR_JPEGR_INVALID_INPUT_TYPE;
@@ -1277,6 +1275,7 @@
                         primary_jpg_image_ptr,
                         decoder.getEXIFPos(),
                         decoder.getEXIFSize());
+    dest_data.reset(reinterpret_cast<uint8_t*>(new_jpg_image.data));
     exif_from_jpg.data = decoder.getEXIFPtr();
     exif_from_jpg.length = decoder.getEXIFSize();
     pExif = &exif_from_jpg;
diff --git a/services/gpuservice/Android.bp b/services/gpuservice/Android.bp
index 052efb6b..e5b1e44 100644
--- a/services/gpuservice/Android.bp
+++ b/services/gpuservice/Android.bp
@@ -21,7 +21,13 @@
 
 cc_defaults {
     name: "libgpuservice_defaults",
-    defaults: ["gpuservice_defaults"],
+    defaults: [
+        "gpuservice_defaults",
+        "libvkjson_deps",
+        "libgfxstats_deps",
+        "libgpumem_deps",
+        "libgpumemtracer_deps",
+    ],
     cflags: [
         "-DLOG_TAG=\"GpuService\"",
     ],
@@ -29,17 +35,17 @@
         "libbase",
         "libbinder",
         "libcutils",
-        "libgfxstats",
-        "libgpumem",
         "libgpuwork",
-        "libgpumemtracer",
         "libgraphicsenv",
         "liblog",
         "libutils",
-        "libvkjson",
     ],
     static_libs: [
+        "libgfxstats",
+        "libgpumem",
+        "libgpumemtracer",
         "libserviceutils",
+        "libvkjson",
     ],
     export_static_lib_headers: [
         "libserviceutils",
@@ -68,7 +74,7 @@
     ],
 }
 
-cc_library_shared {
+cc_library_static {
     name: "libgpuservice",
     defaults: ["libgpuservice_production_defaults"],
     export_include_dirs: ["include"],
@@ -96,14 +102,17 @@
 
 cc_binary {
     name: "gpuservice",
-    defaults: ["libgpuservice_binary"],
+    defaults: [
+        "libgpuservice_binary",
+        "libgpuservice_production_defaults",
+    ],
     init_rc: ["gpuservice.rc"],
     required: [
         "bpfloader",
         "gpuMem.o",
     ],
     srcs: [":gpuservice_binary_sources"],
-    shared_libs: [
+    static_libs: [
         "libgpuservice",
     ],
 }
diff --git a/services/gpuservice/gpumem/Android.bp b/services/gpuservice/gpumem/Android.bp
index d0ea856..66a3059 100644
--- a/services/gpuservice/gpumem/Android.bp
+++ b/services/gpuservice/gpumem/Android.bp
@@ -21,12 +21,8 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
-cc_library_shared {
-    name: "libgpumem",
-    srcs: [
-        "GpuMem.cpp",
-    ],
-    header_libs: ["bpf_headers"],
+cc_defaults {
+    name: "libgpumem_deps",
     shared_libs: [
         "libbase",
         "libbpf_bcc",
@@ -34,6 +30,17 @@
         "liblog",
         "libutils",
     ],
+}
+
+cc_library_static {
+    name: "libgpumem",
+    defaults: [
+        "libgpumem_deps",
+    ],
+    srcs: [
+        "GpuMem.cpp",
+    ],
+    header_libs: ["bpf_headers"],
     export_include_dirs: ["include"],
     export_header_lib_headers: ["bpf_headers"],
     export_shared_lib_headers: ["libbase"],
diff --git a/services/gpuservice/gpustats/Android.bp b/services/gpuservice/gpustats/Android.bp
index 54291ad..0e64716 100644
--- a/services/gpuservice/gpustats/Android.bp
+++ b/services/gpuservice/gpustats/Android.bp
@@ -7,11 +7,8 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
-cc_library_shared {
-    name: "libgfxstats",
-    srcs: [
-        "GpuStats.cpp",
-    ],
+cc_defaults {
+    name: "libgfxstats_deps",
     shared_libs: [
         "libcutils",
         "libgraphicsenv",
@@ -22,6 +19,16 @@
         "libstatssocket",
         "libutils",
     ],
+}
+
+cc_library_static {
+    name: "libgfxstats",
+    defaults: [
+        "libgfxstats_deps",
+    ],
+    srcs: [
+        "GpuStats.cpp",
+    ],
     export_include_dirs: ["include"],
     export_shared_lib_headers: [
         "libstatspull",
diff --git a/services/gpuservice/tests/fuzzers/Android.bp b/services/gpuservice/tests/fuzzers/Android.bp
index 6bcc5e8..d4d48c4 100644
--- a/services/gpuservice/tests/fuzzers/Android.bp
+++ b/services/gpuservice/tests/fuzzers/Android.bp
@@ -5,10 +5,12 @@
 cc_fuzz {
     name: "gpu_service_fuzzer",
     defaults: [
+        "libgpuservice_defaults",
         "service_fuzzer_defaults",
         "fuzzer_disable_leaks",
     ],
     static_libs: [
+        "libgpuservice",
         "liblog",
     ],
     fuzz_config: {
@@ -20,7 +22,4 @@
     },
     include_dirs: ["frameworks/native/services/gpuservice/"],
     srcs: ["GpuServiceFuzzer.cpp"],
-    shared_libs: [
-        "libgpuservice",
-    ],
 }
diff --git a/services/gpuservice/tests/unittests/Android.bp b/services/gpuservice/tests/unittests/Android.bp
index c870b17..8056a2c 100644
--- a/services/gpuservice/tests/unittests/Android.bp
+++ b/services/gpuservice/tests/unittests/Android.bp
@@ -24,6 +24,9 @@
 cc_test {
     name: "gpuservice_unittest",
     test_suites: ["device-tests"],
+    defaults: [
+        "libgpuservice_defaults",
+    ],
     srcs: [
         "GpuMemTest.cpp",
         "GpuMemTracerTest.cpp",
@@ -36,9 +39,6 @@
         "libbinder",
         "libbpf_bcc",
         "libcutils",
-        "libgfxstats",
-        "libgpumem",
-        "libgpumemtracer",
         "libgraphicsenv",
         "liblog",
         "libprotobuf-cpp-lite",
@@ -46,10 +46,10 @@
         "libstatslog",
         "libstatspull",
         "libutils",
-        "libgpuservice",
     ],
     static_libs: [
         "libgmock",
+        "libgpuservice",
         "libperfetto_client_experimental",
         "perfetto_trace_protos",
     ],
diff --git a/services/gpuservice/tracing/Android.bp b/services/gpuservice/tracing/Android.bp
index a1bc1ed..d636b7d 100644
--- a/services/gpuservice/tracing/Android.bp
+++ b/services/gpuservice/tracing/Android.bp
@@ -21,20 +21,28 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
-cc_library_shared {
-    name: "libgpumemtracer",
-    srcs: [
-        "GpuMemTracer.cpp",
-    ],
+cc_defaults {
+    name: "libgpumemtracer_deps",
     shared_libs: [
-        "libgpumem",
         "libbase",
         "liblog",
         "libutils",
     ],
     static_libs: [
+        "libgpumem",
         "libperfetto_client_experimental",
     ],
+}
+
+cc_library_static {
+    name: "libgpumemtracer",
+    defaults: [
+        "libgpumemtracer_deps",
+        "libgpumem_deps",
+    ],
+    srcs: [
+        "GpuMemTracer.cpp",
+    ],
     export_include_dirs: ["include"],
     export_static_lib_headers: [
         "libperfetto_client_experimental",
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index 7451037..5d1d4af 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -240,9 +240,6 @@
         "libinputservice_test",
         "Bug-115739809",
         "StructLayout_test",
-        // currently unused, but still must build correctly
-        "inputflinger",
-        "libinputflingerhost",
 
         // rust targets
         "libinput_rust_test",
diff --git a/services/inputflinger/InputDeviceMetricsCollector.cpp b/services/inputflinger/InputDeviceMetricsCollector.cpp
index 7c99a1c..56a3fb4 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.cpp
+++ b/services/inputflinger/InputDeviceMetricsCollector.cpp
@@ -41,7 +41,13 @@
 
 constexpr size_t INTERACTIONS_QUEUE_CAPACITY = 500;
 
-int32_t linuxBusToInputDeviceBusEnum(int32_t linuxBus) {
+int32_t linuxBusToInputDeviceBusEnum(int32_t linuxBus, bool isUsiStylus) {
+    if (isUsiStylus) {
+        // This is a stylus connected over the Universal Stylus Initiative (USI) protocol.
+        // For metrics purposes, we treat this protocol as a separate bus.
+        return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__USI;
+    }
+
     // When adding cases to this switch, also add them to the copy of this method in
     // TouchpadInputMapper.cpp.
     // TODO(b/286394420): deduplicate this method with the one in TouchpadInputMapper.cpp.
@@ -58,11 +64,12 @@
 class : public InputDeviceMetricsLogger {
     nanoseconds getCurrentTime() override { return nanoseconds(systemTime(SYSTEM_TIME_MONOTONIC)); }
 
-    void logInputDeviceUsageReported(const InputDeviceIdentifier& identifier,
+    void logInputDeviceUsageReported(const InputDeviceInfo& info,
                                      const DeviceUsageReport& report) override {
         const int32_t durationMillis =
                 std::chrono::duration_cast<std::chrono::milliseconds>(report.usageDuration).count();
         const static std::vector<int32_t> empty;
+        const auto& identifier = info.getIdentifier();
 
         ALOGD_IF(DEBUG, "Usage session reported for device: %s", identifier.name.c_str());
         ALOGD_IF(DEBUG, "    Total duration: %dms", durationMillis);
@@ -90,7 +97,9 @@
                      durMillis);
         }
         util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, identifier.vendor, identifier.product,
-                          identifier.version, linuxBusToInputDeviceBusEnum(identifier.bus),
+                          identifier.version,
+                          linuxBusToInputDeviceBusEnum(identifier.bus,
+                                                       info.getUsiVersion().has_value()),
                           durationMillis, sources, durationsPerSource, uids, durationsPerUid);
     }
 } sStatsdLogger;
@@ -295,21 +304,21 @@
         if (newDeviceInfos.count(deviceId) != 0) {
             continue;
         }
-        onInputDeviceRemoved(deviceId, info.getIdentifier());
+        onInputDeviceRemoved(deviceId, info);
     }
 
     std::swap(newDeviceInfos, mLoggedDeviceInfos);
 }
 
 void InputDeviceMetricsCollector::onInputDeviceRemoved(DeviceId deviceId,
-                                                       const InputDeviceIdentifier& identifier) {
+                                                       const InputDeviceInfo& info) {
     auto it = mActiveUsageSessions.find(deviceId);
     if (it == mActiveUsageSessions.end()) {
         return;
     }
     // Report usage for that device if there is an active session.
     auto& [_, activeSession] = *it;
-    mLogger.logInputDeviceUsageReported(identifier, activeSession.finishSession());
+    mLogger.logInputDeviceUsageReported(info, activeSession.finishSession());
     mActiveUsageSessions.erase(it);
 
     // We don't remove this from mLoggedDeviceInfos because it will be updated in
@@ -365,8 +374,7 @@
         auto activeSessionIt = mActiveUsageSessions.find(deviceId);
         LOG_ALWAYS_FATAL_IF(activeSessionIt == mActiveUsageSessions.end());
         auto& [_, activeSession] = *activeSessionIt;
-        mLogger.logInputDeviceUsageReported(infoIt->second.getIdentifier(),
-                                            activeSession.finishSession());
+        mLogger.logInputDeviceUsageReported(infoIt->second, activeSession.finishSession());
         mActiveUsageSessions.erase(activeSessionIt);
     }
 }
diff --git a/services/inputflinger/InputDeviceMetricsCollector.h b/services/inputflinger/InputDeviceMetricsCollector.h
index c70e6d4..1f7c5d9 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.h
+++ b/services/inputflinger/InputDeviceMetricsCollector.h
@@ -110,8 +110,7 @@
         UidUsageBreakdown uidBreakdown;
     };
 
-    virtual void logInputDeviceUsageReported(const InputDeviceIdentifier&,
-                                             const DeviceUsageReport&) = 0;
+    virtual void logInputDeviceUsageReported(const InputDeviceInfo&, const DeviceUsageReport&) = 0;
     virtual ~InputDeviceMetricsLogger() = default;
 };
 
@@ -189,7 +188,7 @@
     std::map<DeviceId, ActiveSession> mActiveUsageSessions;
 
     void onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos);
-    void onInputDeviceRemoved(DeviceId deviceId, const InputDeviceIdentifier& identifier);
+    void onInputDeviceRemoved(DeviceId deviceId, const InputDeviceInfo& info);
     using SourceProvider = std::function<std::set<InputDeviceUsageSource>(const InputDeviceInfo&)>;
     void onInputDeviceUsage(DeviceId deviceId, std::chrono::nanoseconds eventTime,
                             const SourceProvider& getSources);
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index cb369a8..fa8f548 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -269,6 +269,11 @@
     return msg;
 }
 
+std::ostream& operator<<(std::ostream& out, const MotionEntry& motionEntry) {
+    out << motionEntry.getDescription();
+    return out;
+}
+
 // --- SensorEntry ---
 
 SensorEntry::SensorEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source,
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index 8dc2a2a..dd4aab8 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -24,6 +24,7 @@
 #include <stdint.h>
 #include <utils/Timers.h>
 #include <functional>
+#include <ostream>
 #include <string>
 
 namespace android::inputdispatcher {
@@ -189,6 +190,8 @@
     ~MotionEntry() override;
 };
 
+std::ostream& operator<<(std::ostream& out, const MotionEntry& motionEntry);
+
 struct SensorEntry : EventEntry {
     int32_t deviceId;
     uint32_t source;
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 0a143e8..9cd1e09 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -668,7 +668,15 @@
         } else {
             // This pointer was already sent to the window. Use ACTION_HOVER_MOVE.
             if (CC_UNLIKELY(maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE)) {
-                LOG(FATAL) << "Expected ACTION_HOVER_MOVE instead of " << entry.getDescription();
+                android::base::LogSeverity severity = android::base::LogSeverity::FATAL;
+                if (entry.flags & AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT) {
+                    // The Accessibility injected touch exploration event stream
+                    // has known inconsistencies, so log ERROR instead of
+                    // crashing the device with FATAL.
+                    // TODO(b/299977100): Move a11y severity back to FATAL.
+                    severity = android::base::LogSeverity::ERROR;
+                }
+                LOG(severity) << "Expected ACTION_HOVER_MOVE instead of " << entry.getDescription();
             }
             touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_IS;
         }
@@ -706,6 +714,40 @@
     });
 }
 
+/**
+ * In general, touch should be always split between windows. Some exceptions:
+ * 1. Don't split touch if all of the below is true:
+ *     (a) we have an active pointer down *and*
+ *     (b) a new pointer is going down that's from the same device *and*
+ *     (c) the window that's receiving the current pointer does not support split touch.
+ * 2. Don't split mouse events
+ */
+bool shouldSplitTouch(const TouchState& touchState, const MotionEntry& entry) {
+    if (isFromSource(entry.source, AINPUT_SOURCE_MOUSE)) {
+        // We should never split mouse events
+        return false;
+    }
+    for (const TouchedWindow& touchedWindow : touchState.windows) {
+        if (touchedWindow.windowHandle->getInfo()->isSpy()) {
+            // Spy windows should not affect whether or not touch is split.
+            continue;
+        }
+        if (touchedWindow.windowHandle->getInfo()->supportsSplitTouch()) {
+            continue;
+        }
+        if (touchedWindow.windowHandle->getInfo()->inputConfig.test(
+                    gui::WindowInfo::InputConfig::IS_WALLPAPER)) {
+            // Wallpaper window should not affect whether or not touch is split
+            continue;
+        }
+
+        if (touchedWindow.hasTouchingPointers(entry.deviceId)) {
+            return false;
+        }
+    }
+    return true;
+}
+
 } // namespace
 
 // --- InputDispatcher ---
@@ -2210,40 +2252,6 @@
     return responsiveMonitors;
 }
 
-/**
- * In general, touch should be always split between windows. Some exceptions:
- * 1. Don't split touch is if we have an active pointer down, and a new pointer is going down that's
- *    from the same device, *and* the window that's receiving the current pointer does not support
- *    split touch.
- * 2. Don't split mouse events
- */
-bool InputDispatcher::shouldSplitTouch(const TouchState& touchState,
-                                       const MotionEntry& entry) const {
-    if (isFromSource(entry.source, AINPUT_SOURCE_MOUSE)) {
-        // We should never split mouse events
-        return false;
-    }
-    for (const TouchedWindow& touchedWindow : touchState.windows) {
-        if (touchedWindow.windowHandle->getInfo()->isSpy()) {
-            // Spy windows should not affect whether or not touch is split.
-            continue;
-        }
-        if (touchedWindow.windowHandle->getInfo()->supportsSplitTouch()) {
-            continue;
-        }
-        if (touchedWindow.windowHandle->getInfo()->inputConfig.test(
-                    gui::WindowInfo::InputConfig::IS_WALLPAPER)) {
-            // Wallpaper window should not affect whether or not touch is split
-            continue;
-        }
-
-        if (touchedWindow.hasTouchingPointers(entry.deviceId)) {
-            return false;
-        }
-    }
-    return true;
-}
-
 std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked(
         nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions,
         InputEventInjectionResult& outInjectionResult) {
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 512bc4f..4d145c6 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -522,7 +522,6 @@
     // shade is pulled down while we are counting down the timeout).
     void resetNoFocusedWindowTimeoutLocked() REQUIRES(mLock);
 
-    bool shouldSplitTouch(const TouchState& touchState, const MotionEntry& entry) const;
     int32_t getTargetDisplayId(const EventEntry& entry);
     sp<android::gui::WindowInfoHandle> findFocusedWindowTargetLocked(
             nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index 4221e42..9dcf615 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -288,4 +288,9 @@
     return out;
 }
 
+std::ostream& operator<<(std::ostream& out, const TouchState& state) {
+    out << state.dump();
+    return out;
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 39e63e5..f016936 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <bitset>
+#include <ostream>
 #include <set>
 #include "TouchedWindow.h"
 
@@ -79,5 +80,7 @@
     std::string dump() const;
 };
 
+std::ostream& operator<<(std::ostream& out, const TouchState& state);
+
 } // namespace inputdispatcher
 } // namespace android
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index 9807a6d..ff4b425 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -256,5 +256,10 @@
     return out;
 }
 
+std::ostream& operator<<(std::ostream& out, const TouchedWindow& window) {
+    out << window.dump();
+    return out;
+}
+
 } // namespace inputdispatcher
 } // namespace android
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index 0a38f9f..3f760c0 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -20,6 +20,7 @@
 #include <input/Input.h>
 #include <utils/BitSet.h>
 #include <bitset>
+#include <ostream>
 #include <set>
 #include "InputTarget.h"
 
@@ -92,5 +93,7 @@
     static std::string deviceStateToString(const TouchedWindow::DeviceState& state);
 };
 
+std::ostream& operator<<(std::ostream& out, const TouchedWindow& window);
+
 } // namespace inputdispatcher
 } // namespace android
diff --git a/services/inputflinger/host/Android.bp b/services/inputflinger/host/Android.bp
deleted file mode 100644
index 4d2839f..0000000
--- a/services/inputflinger/host/Android.bp
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (C) 2015 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.
-
-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_shared {
-    name: "libinputflingerhost",
-    cpp_std: "c++20",
-    srcs: [
-        "InputFlinger.cpp",
-        "InputDriver.cpp",
-        "InputHost.cpp",
-    ],
-
-    header_libs: ["jni_headers"],
-    shared_libs: [
-        "libbase",
-        "libbinder",
-        "libcrypto",
-        "libcutils",
-        "libinput",
-        "liblog",
-        "libutils",
-        "libhardware",
-    ],
-    static_libs: [
-        "libarect",
-    ],
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wno-unused-parameter",
-        // TODO: Move inputflinger to its own process and mark it hidden
-        //-fvisibility=hidden
-    ],
-
-    export_header_lib_headers: ["jni_headers"],
-    export_include_dirs: ["."],
-}
-
-//#######################################################################
-// build input flinger executable
-cc_binary {
-    name: "inputflinger",
-
-    srcs: ["main.cpp"],
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
-
-    shared_libs: [
-        "libbase",
-        "libbinder",
-        "libinputflingerhost",
-        "libutils",
-        "libinput",
-    ],
-    static_libs: [
-        "libarect",
-        "libui-types",
-    ],
-}
diff --git a/services/inputflinger/host/InputDriver.cpp b/services/inputflinger/host/InputDriver.cpp
deleted file mode 100644
index de99fc7..0000000
--- a/services/inputflinger/host/InputDriver.cpp
+++ /dev/null
@@ -1,411 +0,0 @@
-/*
- * Copyright (C) 2015 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 <functional>
-#include <stdint.h>
-#include <sys/types.h>
-#include <unordered_map>
-#include <vector>
-
-#define LOG_TAG "InputDriver"
-
-#define LOG_NDEBUG 0
-
-#include "InputDriver.h"
-#include "InputHost.h"
-
-#include <hardware/input.h>
-#include <input/InputDevice.h>
-#include <input/PropertyMap.h>
-#include <utils/Log.h>
-#include <utils/String8.h>
-
-#define INDENT2 "    "
-
-struct input_property_map {
-    std::unique_ptr<android::PropertyMap> propertyMap;
-};
-
-struct input_property {
-    android::String8 key;
-    android::String8 value;
-};
-
-struct input_device_identifier {
-    const char* name;
-    const char* uniqueId;
-    input_bus_t bus;
-    int32_t     vendorId;
-    int32_t     productId;
-    int32_t     version;
-};
-
-struct input_device_definition {
-    std::vector<input_report_definition*> reportDefs;
-};
-
-struct input_device_handle {
-    input_device_identifier_t* id;
-    input_device_definition_t* def;
-};
-
-struct input_int_usage {
-    input_usage_t usage;
-    int32_t min;
-    int32_t max;
-    float   resolution;
-};
-
-struct input_collection {
-    int32_t arity;
-    std::vector<input_int_usage> intUsages;
-    std::vector<input_usage_t> boolUsages;
-};
-
-struct InputCollectionIdHasher {
-    std::size_t operator()(const input_collection_id& id) const {
-        return std::hash<int>()(static_cast<int>(id));
-    }
-};
-
-struct input_report_definition {
-    std::unordered_map<input_collection_id_t, input_collection, InputCollectionIdHasher> collections;
-};
-
-
-namespace android {
-
-static input_host_callbacks_t kCallbacks = {
-    .create_device_identifier = create_device_identifier,
-    .create_device_definition = create_device_definition,
-    .create_input_report_definition = create_input_report_definition,
-    .create_output_report_definition = create_output_report_definition,
-    .free_report_definition = free_report_definition,
-    .input_device_definition_add_report = input_device_definition_add_report,
-    .input_report_definition_add_collection = input_report_definition_add_collection,
-    .input_report_definition_declare_usage_int = input_report_definition_declare_usage_int,
-    .input_report_definition_declare_usages_bool = input_report_definition_declare_usages_bool,
-    .register_device = register_device,
-    .input_allocate_report = input_allocate_report,
-    .input_report_set_usage_int = input_report_set_usage_int,
-    .input_report_set_usage_bool = input_report_set_usage_bool,
-    .report_event = report_event,
-    .input_get_device_property_map = input_get_device_property_map,
-    .input_get_device_property = input_get_device_property,
-    .input_get_property_key = input_get_property_key,
-    .input_get_property_value = input_get_property_value,
-    .input_free_device_property = input_free_device_property,
-    .input_free_device_property_map = input_free_device_property_map,
-};
-
-InputDriver::InputDriver(const char* name) : mName(String8(name)) {
-    const hw_module_t* module;
-    int err = input_open(&module, name);
-    LOG_ALWAYS_FATAL_IF(err != 0, "Input module %s not found", name);
-    mHal = reinterpret_cast<const input_module_t*>(module);
-}
-
-void InputDriver::init() {
-    mHal->init(mHal, static_cast<input_host_t*>(this), kCallbacks);
-}
-
-input_device_identifier_t* InputDriver::createDeviceIdentifier(
-            const char* name, int32_t productId, int32_t vendorId,
-            input_bus_t bus, const char* uniqueId) {
-    auto identifier = new ::input_device_identifier {
-        .name = name,
-        .uniqueId = uniqueId,
-        .bus = bus,
-        .vendorId = vendorId,
-        .productId = productId,
-    };
-    // TODO: store this identifier somewhere
-    return identifier;
-}
-
-input_device_definition_t* InputDriver::createDeviceDefinition() {
-    return new ::input_device_definition;
-}
-
-input_report_definition_t* InputDriver::createInputReportDefinition() {
-    return new ::input_report_definition;
-}
-
-input_report_definition_t* InputDriver::createOutputReportDefinition() {
-    return new ::input_report_definition;
-}
-
-void InputDriver::freeReportDefinition(input_report_definition_t* reportDef) {
-    delete reportDef;
-}
-
-void InputDriver::inputDeviceDefinitionAddReport(input_device_definition_t* d,
-        input_report_definition_t* r) {
-    d->reportDefs.push_back(r);
-}
-
-void InputDriver::inputReportDefinitionAddCollection(input_report_definition_t* report,
-        input_collection_id_t id, int32_t arity) {
-    report->collections[id] = {.arity = arity};
-}
-
-void InputDriver::inputReportDefinitionDeclareUsageInt(input_report_definition_t* report,
-        input_collection_id_t id, input_usage_t usage, int32_t min, int32_t max,
-        float resolution) {
-    if (report->collections.find(id) != report->collections.end()) {
-        report->collections[id].intUsages.push_back({
-                .usage = usage, .min = min, .max = max, .resolution = resolution});
-    }
-}
-
-void InputDriver::inputReportDefinitionDeclareUsagesBool(input_report_definition_t* report,
-        input_collection_id_t id, input_usage_t* usage, size_t usageCount) {
-    if (report->collections.find(id) != report->collections.end()) {
-        for (size_t i = 0; i < usageCount; ++i) {
-            report->collections[id].boolUsages.push_back(usage[i]);
-        }
-    }
-}
-
-input_device_handle_t* InputDriver::registerDevice(input_device_identifier_t* id,
-        input_device_definition_t* d) {
-    ALOGD("Registering device %s with %zu input reports", id->name, d->reportDefs.size());
-    // TODO: save this device handle
-    return new input_device_handle{ .id = id, .def = d };
-}
-
-void InputDriver::unregisterDevice(input_device_handle_t* handle) {
-    delete handle;
-}
-
-input_report_t* InputDriver::inputAllocateReport(input_report_definition_t* r) {
-    ALOGD("Allocating input report for definition %p", r);
-    return nullptr;
-}
-
-void InputDriver::inputReportSetUsageInt(input_report_t* r, input_collection_id_t id,
-        input_usage_t usage, int32_t value, int32_t arity_index) {
-}
-
-void InputDriver::inputReportSetUsageBool(input_report_t* r, input_collection_id_t id,
-        input_usage_t usage, bool value, int32_t arity_index) {
-}
-
-void InputDriver::reportEvent(input_device_handle_t* d, input_report_t* report) {
-    ALOGD("report_event %p for handle %p", report, d);
-}
-
-input_property_map_t* InputDriver::inputGetDevicePropertyMap(input_device_identifier_t* id) {
-    InputDeviceIdentifier idi;
-    idi.name = id->name;
-    idi.uniqueId = id->uniqueId;
-    idi.bus = id->bus;
-    idi.vendor = id->vendorId;
-    idi.product = id->productId;
-    idi.version = id->version;
-
-    std::string configFile =
-            getInputDeviceConfigurationFilePathByDeviceIdentifier(idi,
-                                                                  InputDeviceConfigurationFileType::
-                                                                          CONFIGURATION);
-    if (configFile.empty()) {
-        ALOGD("No input device configuration file found for device '%s'.",
-                idi.name.c_str());
-    } else {
-        std::unique_ptr<input_property_map_t> propMap = std::make_unique<input_property_map_t>();
-        android::base::Result<std::unique_ptr<PropertyMap>> result =
-                PropertyMap::load(configFile.c_str());
-        if (!result.ok()) {
-            ALOGE("Error loading input device configuration file for device '%s'. "
-                    "Using default configuration.",
-                    idi.name.c_str());
-            return nullptr;
-        }
-        propMap->propertyMap = std::move(*result);
-        return propMap.release();
-    }
-    return nullptr;
-}
-
-input_property_t* InputDriver::inputGetDeviceProperty(input_property_map_t* map, const char* key) {
-    if (map != nullptr) {
-        std::optional<std::string> value = map->propertyMap->getString(key);
-        if (!value.has_value()) {
-            return nullptr;
-        }
-        auto prop = std::make_unique<input_property_t>();
-        prop->key = key;
-        prop->value = value->c_str();
-        return prop.release();
-    }
-    return nullptr;
-}
-
-const char* InputDriver::inputGetPropertyKey(input_property_t* property) {
-    if (property != nullptr) {
-        return property->key.c_str();
-    }
-    return nullptr;
-}
-
-const char* InputDriver::inputGetPropertyValue(input_property_t* property) {
-    if (property != nullptr) {
-        return property->value.c_str();
-    }
-    return nullptr;
-}
-
-void InputDriver::inputFreeDeviceProperty(input_property_t* property) {
-    if (property != nullptr) {
-        delete property;
-    }
-}
-
-void InputDriver::inputFreeDevicePropertyMap(input_property_map_t* map) {
-    if (map != nullptr) {
-        delete map;
-    }
-}
-
-void InputDriver::dump(String8& result) {
-    result.appendFormat(INDENT2 "HAL Input Driver (%s)\n", mName.c_str());
-}
-
-} // namespace android
-
-// HAL wrapper functions
-
-namespace android {
-
-::input_device_identifier_t* create_device_identifier(input_host_t* host,
-        const char* name, int32_t product_id, int32_t vendor_id,
-        input_bus_t bus, const char* unique_id) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->createDeviceIdentifier(name, product_id, vendor_id, bus, unique_id);
-}
-
-input_device_definition_t* create_device_definition(input_host_t* host) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->createDeviceDefinition();
-}
-
-input_report_definition_t* create_input_report_definition(input_host_t* host) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->createInputReportDefinition();
-}
-
-input_report_definition_t* create_output_report_definition(input_host_t* host) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->createOutputReportDefinition();
-}
-
-void free_report_definition(input_host_t* host, input_report_definition_t* report_def) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->freeReportDefinition(report_def);
-}
-
-void input_device_definition_add_report(input_host_t* host,
-        input_device_definition_t* d, input_report_definition_t* r) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->inputDeviceDefinitionAddReport(d, r);
-}
-
-void input_report_definition_add_collection(input_host_t* host,
-        input_report_definition_t* report, input_collection_id_t id, int32_t arity) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->inputReportDefinitionAddCollection(report, id, arity);
-}
-
-void input_report_definition_declare_usage_int(input_host_t* host,
-        input_report_definition_t* report, input_collection_id_t id,
-        input_usage_t usage, int32_t min, int32_t max, float resolution) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->inputReportDefinitionDeclareUsageInt(report, id, usage, min, max, resolution);
-}
-
-void input_report_definition_declare_usages_bool(input_host_t* host,
-        input_report_definition_t* report, input_collection_id_t id,
-        input_usage_t* usage, size_t usage_count) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->inputReportDefinitionDeclareUsagesBool(report, id, usage, usage_count);
-}
-
-input_device_handle_t* register_device(input_host_t* host,
-        input_device_identifier_t* id, input_device_definition_t* d) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->registerDevice(id, d);
-}
-
-void unregister_device(input_host_t* host, input_device_handle_t* handle) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->unregisterDevice(handle);
-}
-
-input_report_t* input_allocate_report(input_host_t* host, input_report_definition_t* r) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->inputAllocateReport(r);
-}
-
-void input_report_set_usage_int(input_host_t* host, input_report_t* r,
-        input_collection_id_t id, input_usage_t usage, int32_t value, int32_t arity_index) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->inputReportSetUsageInt(r, id, usage, value, arity_index);
-}
-
-void input_report_set_usage_bool(input_host_t* host, input_report_t* r,
-        input_collection_id_t id, input_usage_t usage, bool value, int32_t arity_index) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->inputReportSetUsageBool(r, id, usage, value, arity_index);
-}
-
-void report_event(input_host_t* host, input_device_handle_t* d, input_report_t* report) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->reportEvent(d, report);
-}
-
-input_property_map_t* input_get_device_property_map(input_host_t* host,
-        input_device_identifier_t* id) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->inputGetDevicePropertyMap(id);
-}
-
-input_property_t* input_get_device_property(input_host_t* host, input_property_map_t* map,
-        const char* key) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->inputGetDeviceProperty(map, key);
-}
-
-const char* input_get_property_key(input_host_t* host, input_property_t* property) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->inputGetPropertyKey(property);
-}
-
-const char* input_get_property_value(input_host_t* host, input_property_t* property) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    return driver->inputGetPropertyValue(property);
-}
-
-void input_free_device_property(input_host_t* host, input_property_t* property) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->inputFreeDeviceProperty(property);
-}
-
-void input_free_device_property_map(input_host_t* host, input_property_map_t* map) {
-    auto driver = static_cast<InputDriverInterface*>(host);
-    driver->inputFreeDevicePropertyMap(map);
-}
-
-} // namespace android
diff --git a/services/inputflinger/host/InputDriver.h b/services/inputflinger/host/InputDriver.h
deleted file mode 100644
index b4c9094..0000000
--- a/services/inputflinger/host/InputDriver.h
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2015 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 <stdint.h>
-#include <sys/types.h>
-
-#include "InputHost.h"
-
-#include <hardware/input.h>
-#include <utils/RefBase.h>
-#include <utils/String8.h>
-
-// Declare a concrete type for the HAL
-struct input_host {
-};
-
-namespace android {
-
-class InputDriverInterface : public input_host_t, public virtual RefBase {
-protected:
-    InputDriverInterface() = default;
-    virtual ~InputDriverInterface() = default;
-
-public:
-    virtual void init() = 0;
-
-    virtual input_device_identifier_t* createDeviceIdentifier(
-            const char* name, int32_t productId, int32_t vendorId,
-            input_bus_t bus, const char* uniqueId) = 0;
-    virtual input_device_definition_t* createDeviceDefinition() = 0;
-    virtual input_report_definition_t* createInputReportDefinition() = 0;
-    virtual input_report_definition_t* createOutputReportDefinition() = 0;
-    virtual void freeReportDefinition(input_report_definition_t* reportDef) = 0;
-
-    virtual void inputDeviceDefinitionAddReport(input_device_definition_t* d,
-            input_report_definition_t* r) = 0;
-    virtual void inputReportDefinitionAddCollection(input_report_definition_t* report,
-            input_collection_id_t id, int32_t arity) = 0;
-    virtual void inputReportDefinitionDeclareUsageInt(input_report_definition_t* report,
-            input_collection_id_t id, input_usage_t usage, int32_t min, int32_t max,
-            float resolution) = 0;
-    virtual void inputReportDefinitionDeclareUsagesBool(input_report_definition_t* report,
-            input_collection_id_t id, input_usage_t* usage, size_t usageCount) = 0;
-
-    virtual input_device_handle_t* registerDevice(input_device_identifier_t* id,
-            input_device_definition_t* d) = 0;
-    virtual void unregisterDevice(input_device_handle_t* handle) = 0;
-
-    virtual input_report_t* inputAllocateReport(input_report_definition_t* r) = 0;
-    virtual void inputReportSetUsageInt(input_report_t* r, input_collection_id_t id,
-            input_usage_t usage, int32_t value, int32_t arity_index) = 0;
-    virtual void inputReportSetUsageBool(input_report_t* r, input_collection_id_t id,
-            input_usage_t usage, bool value, int32_t arity_index) = 0;
-    virtual void reportEvent(input_device_handle_t* d, input_report_t* report) = 0;
-
-    virtual input_property_map_t* inputGetDevicePropertyMap(input_device_identifier_t* id) = 0;
-    virtual input_property_t* inputGetDeviceProperty(input_property_map_t* map,
-            const char* key) = 0;
-    virtual const char* inputGetPropertyKey(input_property_t* property) = 0;
-    virtual const char* inputGetPropertyValue(input_property_t* property) = 0;
-    virtual void inputFreeDeviceProperty(input_property_t* property) = 0;
-    virtual void inputFreeDevicePropertyMap(input_property_map_t* map) = 0;
-
-    virtual void dump(String8& result) = 0;
-};
-
-class InputDriver : public InputDriverInterface {
-public:
-    explicit InputDriver(const char* name);
-    virtual ~InputDriver() = default;
-
-    virtual void init() override;
-
-    virtual input_device_identifier_t* createDeviceIdentifier(
-            const char* name, int32_t productId, int32_t vendorId,
-            input_bus_t bus, const char* uniqueId) override;
-    virtual input_device_definition_t* createDeviceDefinition() override;
-    virtual input_report_definition_t* createInputReportDefinition() override;
-    virtual input_report_definition_t* createOutputReportDefinition() override;
-    virtual void freeReportDefinition(input_report_definition_t* reportDef) override;
-
-    virtual void inputDeviceDefinitionAddReport(input_device_definition_t* d,
-            input_report_definition_t* r) override;
-    virtual void inputReportDefinitionAddCollection(input_report_definition_t* report,
-            input_collection_id_t id, int32_t arity) override;
-    virtual void inputReportDefinitionDeclareUsageInt(input_report_definition_t* report,
-            input_collection_id_t id, input_usage_t usage, int32_t min, int32_t max,
-            float resolution) override;
-    virtual void inputReportDefinitionDeclareUsagesBool(input_report_definition_t* report,
-            input_collection_id_t id, input_usage_t* usage, size_t usageCount) override;
-
-    virtual input_device_handle_t* registerDevice(input_device_identifier_t* id,
-            input_device_definition_t* d) override;
-    virtual void unregisterDevice(input_device_handle_t* handle) override;
-
-    virtual input_report_t* inputAllocateReport(input_report_definition_t* r) override;
-    virtual void inputReportSetUsageInt(input_report_t* r, input_collection_id_t id,
-            input_usage_t usage, int32_t value, int32_t arity_index) override;
-    virtual void inputReportSetUsageBool(input_report_t* r, input_collection_id_t id,
-            input_usage_t usage, bool value, int32_t arity_index) override;
-    virtual void reportEvent(input_device_handle_t* d, input_report_t* report) override;
-
-    virtual input_property_map_t* inputGetDevicePropertyMap(input_device_identifier_t* id) override;
-    virtual input_property_t* inputGetDeviceProperty(input_property_map_t* map,
-            const char* key) override;
-    virtual const char* inputGetPropertyKey(input_property_t* property) override;
-    virtual const char* inputGetPropertyValue(input_property_t* property) override;
-    virtual void inputFreeDeviceProperty(input_property_t* property) override;
-    virtual void inputFreeDevicePropertyMap(input_property_map_t* map) override;
-
-    virtual void dump(String8& result) override;
-
-private:
-    String8 mName;
-    const input_module_t* mHal;
-};
-
-
-extern "C" {
-
-input_device_identifier_t* create_device_identifier(input_host_t* host,
-        const char* name, int32_t product_id, int32_t vendor_id,
-        input_bus_t bus, const char* unique_id);
-
-input_device_definition_t* create_device_definition(input_host_t* host);
-
-input_report_definition_t* create_input_report_definition(input_host_t* host);
-
-input_report_definition_t* create_output_report_definition(input_host_t* host);
-
-void free_report_definition(input_host_t* host, input_report_definition_t* report_def);
-
-void input_device_definition_add_report(input_host_t* host,
-        input_device_definition_t* d, input_report_definition_t* r);
-
-void input_report_definition_add_collection(input_host_t* host,
-        input_report_definition_t* report, input_collection_id_t id, int32_t arity);
-
-void input_report_definition_declare_usage_int(input_host_t* host,
-        input_report_definition_t* report, input_collection_id_t id,
-        input_usage_t usage, int32_t min, int32_t max, float resolution);
-
-void input_report_definition_declare_usages_bool(input_host_t* host,
-        input_report_definition_t* report, input_collection_id_t id,
-        input_usage_t* usage, size_t usage_count);
-
-
-input_device_handle_t* register_device(input_host_t* host,
-        input_device_identifier_t* id, input_device_definition_t* d);
-
-void unregister_device(input_host_t* host, input_device_handle_t* handle);
-
-input_report_t* input_allocate_report(input_host_t* host, input_report_definition_t* r);
-
-void input_report_set_usage_int(input_host_t* host, input_report_t* r,
-        input_collection_id_t id, input_usage_t usage, int32_t value, int32_t arity_index);
-
-void input_report_set_usage_bool(input_host_t* host, input_report_t* r,
-        input_collection_id_t id, input_usage_t usage, bool value, int32_t arity_index);
-
-void report_event(input_host_t* host, input_device_handle_t* d, input_report_t* report);
-
-input_property_map_t* input_get_device_property_map(input_host_t* host,
-        input_device_identifier_t* id);
-
-input_property_t* input_get_device_property(input_host_t* host, input_property_map_t* map,
-        const char* key);
-
-const char* input_get_property_key(input_host_t* host, input_property_t* property);
-
-const char* input_get_property_value(input_host_t* host, input_property_t* property);
-
-void input_free_device_property(input_host_t* host, input_property_t* property);
-
-void input_free_device_property_map(input_host_t* host, input_property_map_t* map);
-}
-
-} // namespace android
diff --git a/services/inputflinger/host/InputFlinger.cpp b/services/inputflinger/host/InputFlinger.cpp
deleted file mode 100644
index d974c43..0000000
--- a/services/inputflinger/host/InputFlinger.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2013 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 LOG_TAG "InputFlinger"
-
-#include <stdint.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <binder/IPCThreadState.h>
-#include <binder/PermissionCache.h>
-#include <hardware/input.h>
-#include <log/log.h>
-#include <private/android_filesystem_config.h>
-
-#include "InputFlinger.h"
-#include "InputDriver.h"
-
-namespace android {
-
-const String16 sAccessInputFlingerPermission("android.permission.ACCESS_INPUT_FLINGER");
-const String16 sDumpPermission("android.permission.DUMP");
-
-
-InputFlinger::InputFlinger() :
-        BnInputFlinger() {
-    ALOGI("InputFlinger is starting");
-    mHost = new InputHost();
-    mHost->registerInputDriver(new InputDriver(INPUT_INSTANCE_EVDEV));
-}
-
-InputFlinger::~InputFlinger() {
-}
-
-status_t InputFlinger::dump(int fd, const Vector<String16>& args) {
-    String8 result;
-    const IPCThreadState* ipc = IPCThreadState::self();
-    const int pid = ipc->getCallingPid();
-    const int uid = ipc->getCallingUid();
-    if ((uid != AID_SHELL)
-            && !PermissionCache::checkPermission(sDumpPermission, pid, uid)) {
-        result.appendFormat("Permission Denial: "
-                "can't dump InputFlinger from pid=%d, uid=%d\n", pid, uid);
-    } else {
-        dumpInternal(result);
-    }
-    write(fd, result.c_str(), result.size());
-    return OK;
-}
-
-void InputFlinger::dumpInternal(String8& result) {
-    result.append("INPUT FLINGER (dumpsys inputflinger)\n");
-    mHost->dump(result);
-}
-
-}; // namespace android
diff --git a/services/inputflinger/host/InputFlinger.h b/services/inputflinger/host/InputFlinger.h
deleted file mode 100644
index 388988b..0000000
--- a/services/inputflinger/host/InputFlinger.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2013 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 <stdint.h>
-#include <sys/types.h>
-
-#include "InputHost.h"
-
-#include <android/os/BnInputFlinger.h>
-#include <binder/Binder.h>
-#include <cutils/compiler.h>
-#include <utils/String16.h>
-#include <utils/String8.h>
-#include <utils/StrongPointer.h>
-
-using android::gui::FocusRequest;
-using android::os::BnInputFlinger;
-
-namespace android {
-
-class InputFlinger : public BnInputFlinger {
-public:
-    static char const* getServiceName() ANDROID_API {
-        return "inputflinger";
-    }
-
-    InputFlinger() ANDROID_API;
-
-    status_t dump(int fd, const Vector<String16>& args) override;
-    binder::Status createInputChannel(const std::string&, InputChannel*) override {
-        return binder::Status::ok();
-    }
-    binder::Status removeInputChannel(const sp<IBinder>&) override { return binder::Status::ok(); }
-    binder::Status setFocusedWindow(const FocusRequest&) override { return binder::Status::ok(); }
-
-private:
-    ~InputFlinger() override;
-
-    void dumpInternal(String8& result);
-
-    sp<InputHostInterface> mHost;
-};
-
-} // namespace android
diff --git a/services/inputflinger/host/InputHost.cpp b/services/inputflinger/host/InputHost.cpp
deleted file mode 100644
index 094200a..0000000
--- a/services/inputflinger/host/InputHost.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2015 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 <vector>
-
-#include "InputDriver.h"
-#include "InputHost.h"
-
-#include <utils/Log.h>
-#include <utils/String8.h>
-
-#define INDENT "  "
-
-namespace android {
-
-void InputHost::registerInputDriver(InputDriverInterface* driver) {
-    LOG_ALWAYS_FATAL_IF(driver == nullptr, "Cannot register a nullptr as an InputDriver!");
-    driver->init();
-    mDrivers.push_back(driver);
-}
-
-void InputHost::dump(String8& result) {
-    result.append(INDENT "Input Drivers:\n");
-    for (size_t i = 0; i < mDrivers.size(); i++) {
-        mDrivers[i]->dump(result);
-    }
-}
-
-} // namespace android
diff --git a/services/inputflinger/host/InputHost.h b/services/inputflinger/host/InputHost.h
deleted file mode 100644
index bdc4225..0000000
--- a/services/inputflinger/host/InputHost.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2015 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>
-
-#include <hardware/input.h>
-#include <utils/RefBase.h>
-#include <utils/String8.h>
-#include <utils/StrongPointer.h>
-
-#include "InputDriver.h"
-
-namespace android {
-
-class InputDriverInterface;
-
-class InputHostInterface : public virtual RefBase {
-protected:
-    InputHostInterface() = default;
-    virtual ~InputHostInterface() = default;
-
-public:
-
-    virtual void registerInputDriver(InputDriverInterface* driver) = 0;
-
-    virtual void dump(String8& result) = 0;
-};
-
-class InputHost : public InputHostInterface {
-public:
-    InputHost() = default;
-
-    virtual void registerInputDriver(InputDriverInterface* driver) override;
-
-    virtual void dump(String8& result) override;
-
-private:
-    std::vector<sp<InputDriverInterface>> mDrivers;
-};
-
-} // namespace android
diff --git a/services/inputflinger/host/inputflinger.rc b/services/inputflinger/host/inputflinger.rc
deleted file mode 100644
index 4130ddc..0000000
--- a/services/inputflinger/host/inputflinger.rc
+++ /dev/null
@@ -1,5 +0,0 @@
-service inputflinger /system/bin/inputflinger
-    class main
-    user system
-    group input wakelock
-#    onrestart restart zygote
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index 8fe6411..e1806a0 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -69,6 +69,7 @@
         "mapper/gestures/HardwareProperties.cpp",
         "mapper/gestures/HardwareStateConverter.cpp",
         "mapper/gestures/PropertyProvider.cpp",
+        "mapper/gestures/TimerProvider.cpp",
     ],
 }
 
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 0aea0b3..5766b14 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -946,6 +946,7 @@
         device->dump(dump, eventHubDevStr);
     }
 
+    dump += StringPrintf(INDENT "NextTimeout: %" PRId64 "\n", mNextTimeout);
     dump += INDENT "Configuration:\n";
     dump += INDENT2 "ExcludedDeviceNames: [";
     for (size_t i = 0; i < mConfig.excludedDeviceNames.size(); i++) {
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index b565454..90bd7c9 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -130,7 +130,10 @@
 TouchInputMapper::~TouchInputMapper() {}
 
 uint32_t TouchInputMapper::getSources() const {
-    return mSource;
+    // The SOURCE_BLUETOOTH_STYLUS is added to events dynamically if the current stream is modified
+    // by the external stylus state. That's why we don't add it directly to mSource during
+    // configuration.
+    return mSource | (hasExternalStylus() ? AINPUT_SOURCE_BLUETOOTH_STYLUS : 0);
 }
 
 void TouchInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
@@ -932,9 +935,6 @@
         if (hasStylus()) {
             mSource |= AINPUT_SOURCE_STYLUS;
         }
-        if (hasExternalStylus()) {
-            mSource |= AINPUT_SOURCE_BLUETOOTH_STYLUS;
-        }
     } else if (mParameters.deviceType == Parameters::DeviceType::TOUCH_NAVIGATION) {
         mSource = AINPUT_SOURCE_TOUCH_NAVIGATION;
         mDeviceMode = DeviceMode::NAVIGATION;
@@ -1664,6 +1664,10 @@
                                 mSource, mViewport.displayId, policyFlags,
                                 mLastCookedState.buttonState, mCurrentCookedState.buttonState);
 
+    if (mCurrentCookedState.cookedPointerData.pointerCount == 0) {
+        mCurrentStreamModifiedByExternalStylus = false;
+    }
+
     // Clear some transient state.
     mCurrentRawState.rawVScroll = 0;
     mCurrentRawState.rawHScroll = 0;
@@ -1715,6 +1719,10 @@
 
         mExternalStylusButtonsApplied |= pressedButtons;
         mExternalStylusButtonsApplied &= ~releasedButtons;
+
+        if (mExternalStylusButtonsApplied != 0 || releasedButtons != 0) {
+            mCurrentStreamModifiedByExternalStylus = true;
+        }
     }
 }
 
@@ -1725,6 +1733,8 @@
         return;
     }
 
+    mCurrentStreamModifiedByExternalStylus = true;
+
     float pressure = lastPointerData.isTouching(*mFusedStylusPointerId)
             ? lastPointerData.pointerCoordsForId(*mFusedStylusPointerId)
                       .getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)
@@ -3821,6 +3831,9 @@
             ALOG_ASSERT(false);
         }
     }
+    if (mCurrentStreamModifiedByExternalStylus) {
+        source |= AINPUT_SOURCE_BLUETOOTH_STYLUS;
+    }
 
     const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
     const bool showDirectStylusPointer = mConfig.stylusPointerIconEnabled &&
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index c5dfb00..bd9371d 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -357,6 +357,8 @@
     bool mExternalStylusDataPending;
     // A subset of the buttons in mCurrentRawState that came from an external stylus.
     int32_t mExternalStylusButtonsApplied{0};
+    // True if the current cooked pointer data was modified due to the state of an external stylus.
+    bool mCurrentStreamModifiedByExternalStylus{false};
 
     // True if we sent a HOVER_ENTER event.
     bool mSentHoverEnter{false};
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 6ea004d..c76fec7 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -25,6 +25,7 @@
 
 #include <android-base/stringprintf.h>
 #include <android/input.h>
+#include <com_android_input_flags.h>
 #include <ftl/enum.h>
 #include <input/PrintTools.h>
 #include <linux/input-event-codes.h>
@@ -34,8 +35,11 @@
 #include "TouchCursorInputMapperCommon.h"
 #include "TouchpadInputMapper.h"
 #include "gestures/HardwareProperties.h"
+#include "gestures/TimerProvider.h"
 #include "ui/Rotation.h"
 
+namespace input_flags = com::android::input::flags;
+
 namespace android {
 
 namespace {
@@ -125,7 +129,13 @@
     mapper->consumeGesture(gesture);
 }
 
-int32_t linuxBusToInputDeviceBusEnum(int32_t linuxBus) {
+int32_t linuxBusToInputDeviceBusEnum(int32_t linuxBus, bool isUsiStylus) {
+    if (isUsiStylus) {
+        // This is a stylus connected over the Universal Stylus Initiative (USI) protocol.
+        // For metrics purposes, we treat this protocol as a separate bus.
+        return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__USI;
+    }
+
     // When adding cases to this switch, also add them to the copy of this method in
     // InputDeviceMetricsCollector.cpp.
     // TODO(b/286394420): deduplicate this method with the one in InputDeviceMetricsCollector.cpp.
@@ -199,8 +209,8 @@
         for (auto& [id, counters] : mCounters) {
             auto [busId, vendorId, productId, versionId] = id;
             addAStatsEvent(outEventList, android::util::TOUCHPAD_USAGE, vendorId, productId,
-                           versionId, linuxBusToInputDeviceBusEnum(busId), counters.fingers,
-                           counters.palms, counters.twoFingerSwipeGestures,
+                           versionId, linuxBusToInputDeviceBusEnum(busId, /*isUsi=*/false),
+                           counters.fingers, counters.palms, counters.twoFingerSwipeGestures,
                            counters.threeFingerSwipeGestures, counters.fourFingerSwipeGestures,
                            counters.pinchGestures);
         }
@@ -232,6 +242,7 @@
       : InputMapper(deviceContext, readerConfig),
         mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter),
         mPointerController(getContext()->getPointerController(getDeviceId())),
+        mTimerProvider(*getContext()),
         mStateConverter(deviceContext, mMotionAccumulator),
         mGestureConverter(*getContext(), deviceContext, getDeviceId()),
         mCapturedEventConverter(*getContext(), deviceContext, mMotionAccumulator, getDeviceId()),
@@ -253,8 +264,12 @@
     // 2) TouchpadInputMapper is stored as a unique_ptr and not moved.
     mGestureInterpreter->SetPropProvider(const_cast<GesturesPropProvider*>(&gesturePropProvider),
                                          &mPropertyProvider);
+    if (input_flags::enable_gestures_library_timer_provider()) {
+        mGestureInterpreter->SetTimerProvider(const_cast<GesturesTimerProvider*>(
+                                                      &kGestureTimerProvider),
+                                              &mTimerProvider);
+    }
     mGestureInterpreter->SetCallback(gestureInterpreterCallback, this);
-    // TODO(b/251196347): set a timer provider, so the library can use timers.
 }
 
 TouchpadInputMapper::~TouchpadInputMapper() {
@@ -262,14 +277,14 @@
         mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
     }
 
-    // The gesture interpreter's destructor will call its property provider's free function for all
-    // gesture properties, in this case calling PropertyProvider::freeProperty using a raw pointer
-    // to mPropertyProvider. Depending on the declaration order in TouchpadInputMapper.h, this may
-    // happen after mPropertyProvider has been destructed, causing allocation errors. Depending on
-    // declaration order to avoid crashes seems rather fragile, so explicitly clear the property
-    // provider here to ensure all the freeProperty calls happen before mPropertyProvider is
-    // destructed.
+    // The gesture interpreter's destructor will try to free its property and timer providers,
+    // calling PropertyProvider::freeProperty and TimerProvider::freeTimer using a raw pointers.
+    // Depending on the declaration order in TouchpadInputMapper.h, those providers may have already
+    // been freed, causing allocation errors or use-after-free bugs. Depending on declaration order
+    // to avoid this seems rather fragile, so explicitly clear the providers here to ensure all the
+    // freeProperty and freeTimer calls happen before the providers are destructed.
     mGestureInterpreter->SetPropProvider(nullptr, nullptr);
+    mGestureInterpreter->SetTimerProvider(nullptr, nullptr);
 }
 
 uint32_t TouchpadInputMapper::getSources() const {
@@ -287,9 +302,6 @@
 
 void TouchpadInputMapper::dump(std::string& dump) {
     dump += INDENT2 "Touchpad Input Mapper:\n";
-    if (mProcessing) {
-        dump += INDENT3 "Currently processing a hardware state\n";
-    }
     if (mResettingInterpreter) {
         dump += INDENT3 "Currently resetting gesture interpreter\n";
     }
@@ -298,6 +310,12 @@
     dump += addLinePrefix(mGestureConverter.dump(), INDENT4);
     dump += INDENT3 "Gesture properties:\n";
     dump += addLinePrefix(mPropertyProvider.dump(), INDENT4);
+    if (input_flags::enable_gestures_library_timer_provider()) {
+        dump += INDENT3 "Timer provider:\n";
+        dump += addLinePrefix(mTimerProvider.dump(), INDENT4);
+    } else {
+        dump += INDENT3 "Timer provider: disabled by flag\n";
+    }
     dump += INDENT3 "Captured event converter:\n";
     dump += addLinePrefix(mCapturedEventConverter.dump(), INDENT4);
     dump += StringPrintf(INDENT3 "DisplayId: %s\n", toString(mDisplayId).c_str());
@@ -443,13 +461,18 @@
 std::list<NotifyArgs> TouchpadInputMapper::sendHardwareState(nsecs_t when, nsecs_t readTime,
                                                              SelfContainedHardwareState schs) {
     ALOGD_IF(DEBUG_TOUCHPAD_GESTURES, "New hardware state: %s", schs.state.String().c_str());
-    mProcessing = true;
     mGestureInterpreter->PushHardwareState(&schs.state);
-    mProcessing = false;
-
     return processGestures(when, readTime);
 }
 
+std::list<NotifyArgs> TouchpadInputMapper::timeoutExpired(nsecs_t when) {
+    if (!input_flags::enable_gestures_library_timer_provider()) {
+        return {};
+    }
+    mTimerProvider.triggerCallbacks(when);
+    return processGestures(when, when);
+}
+
 void TouchpadInputMapper::consumeGesture(const Gesture* gesture) {
     ALOGD_IF(DEBUG_TOUCHPAD_GESTURES, "Gesture ready: %s", gesture->String().c_str());
     if (mResettingInterpreter) {
@@ -457,10 +480,6 @@
         // ignore any gestures produced from the interpreter while we're resetting it.
         return;
     }
-    if (!mProcessing) {
-        ALOGE("Received gesture outside of the normal processing flow; ignoring it.");
-        return;
-    }
     mGesturesToProcess.push_back(*gesture);
 }
 
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index 47d712e..a68ae43 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -34,6 +34,7 @@
 #include "gestures/GestureConverter.h"
 #include "gestures/HardwareStateConverter.h"
 #include "gestures/PropertyProvider.h"
+#include "gestures/TimerProvider.h"
 
 #include "include/gestures.h"
 
@@ -56,6 +57,7 @@
                                                     ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> timeoutExpired(nsecs_t when) override;
 
     void consumeGesture(const Gesture* gesture);
 
@@ -80,6 +82,7 @@
     std::shared_ptr<PointerControllerInterface> mPointerController;
 
     PropertyProvider mPropertyProvider;
+    TimerProvider mTimerProvider;
 
     // The MultiTouchMotionAccumulator is shared between the HardwareStateConverter and
     // CapturedTouchpadEventConverter, so that if the touchpad is captured or released while touches
@@ -92,7 +95,6 @@
     CapturedTouchpadEventConverter mCapturedEventConverter;
 
     bool mPointerCaptured = false;
-    bool mProcessing = false;
     bool mResettingInterpreter = false;
     std::vector<Gesture> mGesturesToProcess;
 
diff --git a/services/inputflinger/reader/mapper/gestures/TimerProvider.cpp b/services/inputflinger/reader/mapper/gestures/TimerProvider.cpp
new file mode 100644
index 0000000..df2f260
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/TimerProvider.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2023 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 "TimerProvider.h"
+
+#include <chrono>
+#include <string>
+
+#include <android-base/logging.h>
+#include <input/PrintTools.h>
+
+namespace android {
+
+namespace {
+
+nsecs_t stimeToNsecs(stime_t time) {
+    return std::chrono::duration_cast<std::chrono::nanoseconds>(
+                   std::chrono::duration<stime_t>(time))
+            .count();
+}
+
+stime_t nsecsToStime(nsecs_t time) {
+    return std::chrono::duration_cast<std::chrono::duration<stime_t>>(
+                   std::chrono::nanoseconds(time))
+            .count();
+}
+
+GesturesTimer* createTimer(void* data) {
+    return static_cast<TimerProvider*>(data)->createTimer();
+}
+
+void setDeadline(void* data, GesturesTimer* timer, stime_t delay, GesturesTimerCallback callback,
+                 void* callbackData) {
+    static_cast<TimerProvider*>(data)->setDeadline(timer, stimeToNsecs(delay), callback,
+                                                   callbackData);
+};
+
+void cancelTimer(void* data, GesturesTimer* timer) {
+    static_cast<TimerProvider*>(data)->cancelTimer(timer);
+}
+
+void freeTimer(void* data, GesturesTimer* timer) {
+    static_cast<TimerProvider*>(data)->freeTimer(timer);
+}
+
+} // namespace
+
+const GesturesTimerProvider kGestureTimerProvider = {
+        .create_fn = createTimer,
+        .set_fn = setDeadline,
+        .cancel_fn = cancelTimer,
+        .free_fn = freeTimer,
+};
+
+TimerProvider::TimerProvider(InputReaderContext& context) : mReaderContext(context) {}
+
+std::string TimerProvider::dump() {
+    std::string dump;
+    auto timerPtrToString = [](const std::unique_ptr<GesturesTimer>& timer) {
+        return std::to_string(timer->id);
+    };
+    dump += "Timer IDs: " + dumpVector<std::unique_ptr<GesturesTimer>>(mTimers, timerPtrToString) +
+            "\n";
+    dump += "Deadlines and corresponding timer IDs:\n";
+    dump += addLinePrefix(dumpMap(mDeadlines, constToString,
+                                  [](const Deadline& deadline) {
+                                      return std::to_string(deadline.timerId);
+                                  }),
+                          "  ") +
+            "\n";
+    return dump;
+}
+
+void TimerProvider::triggerCallbacks(nsecs_t when) {
+    while (!mDeadlines.empty() && when >= mDeadlines.begin()->first) {
+        const auto& deadlinePair = mDeadlines.begin();
+        deadlinePair->second.callback(when);
+        mDeadlines.erase(deadlinePair);
+    }
+    requestTimeout();
+}
+
+GesturesTimer* TimerProvider::createTimer() {
+    mTimers.push_back(std::make_unique<GesturesTimer>());
+    mTimers.back()->id = mNextTimerId;
+    mNextTimerId++;
+    return mTimers.back().get();
+}
+
+void TimerProvider::setDeadline(GesturesTimer* timer, nsecs_t delay, GesturesTimerCallback callback,
+                                void* callbackData) {
+    setDeadlineWithoutRequestingTimeout(timer, delay, callback, callbackData);
+    requestTimeout();
+}
+
+void TimerProvider::setDeadlineWithoutRequestingTimeout(GesturesTimer* timer, nsecs_t delay,
+                                                        GesturesTimerCallback callback,
+                                                        void* callbackData) {
+    const nsecs_t now = getCurrentTime();
+    const nsecs_t time = now + delay;
+    std::function<void(nsecs_t)> wrappedCallback = [=, this](nsecs_t triggerTime) {
+        stime_t nextDelay = callback(nsecsToStime(triggerTime), callbackData);
+        if (nextDelay >= 0.0) {
+            // When rescheduling a deadline, we know that we're running inside a call to
+            // triggerCallbacks, at the end of which requestTimeout will be called. This means that
+            // we don't want to call the public setDeadline, as that will request a timeout before
+            // triggerCallbacks has removed this current deadline, resulting in a request for a
+            // timeout that has already passed.
+            setDeadlineWithoutRequestingTimeout(timer, stimeToNsecs(nextDelay), callback,
+                                                callbackData);
+        }
+    };
+    mDeadlines.insert({time, Deadline(wrappedCallback, timer->id)});
+}
+
+void TimerProvider::cancelTimer(GesturesTimer* timer) {
+    int id = timer->id;
+    std::erase_if(mDeadlines, [id](const auto& item) { return item.second.timerId == id; });
+    requestTimeout();
+}
+
+void TimerProvider::freeTimer(GesturesTimer* timer) {
+    cancelTimer(timer);
+    std::erase_if(mTimers, [timer](std::unique_ptr<GesturesTimer>& t) { return t.get() == timer; });
+}
+
+void TimerProvider::requestTimeout() {
+    if (!mDeadlines.empty()) {
+        // Because a std::multimap is sorted by key, we simply use the time for the first entry.
+        mReaderContext.requestTimeoutAtTime(mDeadlines.begin()->first);
+    }
+}
+
+nsecs_t TimerProvider::getCurrentTime() {
+    return systemTime(SYSTEM_TIME_MONOTONIC);
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/reader/mapper/gestures/TimerProvider.h b/services/inputflinger/reader/mapper/gestures/TimerProvider.h
new file mode 100644
index 0000000..7c870e0
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/TimerProvider.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2023 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 <functional>
+#include <list>
+#include <map>
+#include <memory>
+#include <vector>
+
+#include <utils/Timers.h>
+
+#include "InputReaderContext.h"
+#include "NotifyArgs.h"
+#include "include/gestures.h"
+
+namespace android {
+
+extern const GesturesTimerProvider kGestureTimerProvider;
+
+// Implementation of a gestures library timer provider, which allows the library to set and cancel
+// callbacks.
+class TimerProvider {
+public:
+    TimerProvider(InputReaderContext& context);
+    virtual ~TimerProvider() = default;
+
+    // Disable copy and move, since pointers to TimerProvider objects are used in callbacks.
+    TimerProvider(const TimerProvider&) = delete;
+    TimerProvider& operator=(const TimerProvider&) = delete;
+
+    std::string dump();
+    void triggerCallbacks(nsecs_t when);
+
+    // Methods to be called by the gestures library:
+    GesturesTimer* createTimer();
+    void setDeadline(GesturesTimer* timer, nsecs_t delay, GesturesTimerCallback callback,
+                     void* callbackData);
+    void cancelTimer(GesturesTimer* timer);
+    void freeTimer(GesturesTimer* timer);
+
+protected:
+    // A wrapper for the system clock, to allow tests to override it.
+    virtual nsecs_t getCurrentTime();
+
+private:
+    void setDeadlineWithoutRequestingTimeout(GesturesTimer* timer, nsecs_t delay,
+                                             GesturesTimerCallback callback, void* callbackData);
+    // Requests a timeout from the InputReader for the nearest deadline in mDeadlines. Must be
+    // called whenever mDeadlines is modified.
+    void requestTimeout();
+
+    InputReaderContext& mReaderContext;
+    int mNextTimerId = 0;
+    std::vector<std::unique_ptr<GesturesTimer>> mTimers;
+
+    struct Deadline {
+        Deadline(std::function<void(nsecs_t)> callback, int timerId)
+              : callback(callback), timerId(timerId) {}
+        const std::function<void(nsecs_t)> callback;
+        const int timerId;
+    };
+
+    std::multimap<nsecs_t /*time*/, Deadline> mDeadlines;
+};
+
+} // namespace android
+
+// Represents a "timer" registered by the gestures library. In practice, this just means a set of
+// deadlines that can be cancelled as a group. The library's API requires this to be in the
+// top-level namespace.
+struct GesturesTimer {
+    int id = -1;
+};
\ No newline at end of file
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 6410046..d87a5a7 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -63,6 +63,7 @@
         "PropertyProvider_test.cpp",
         "SlopController_test.cpp",
         "SyncQueue_test.cpp",
+        "TimerProvider_test.cpp",
         "TestInputListener.cpp",
         "TestInputListenerMatchers.cpp",
         "TouchpadInputMapper_test.cpp",
diff --git a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
index 2ff64c8..7ccfaca 100644
--- a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
+++ b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
@@ -53,7 +53,7 @@
 constexpr int32_t POINTER_1_DOWN =
         AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
-InputDeviceIdentifier getIdentifier(int32_t id = DEVICE_ID) {
+InputDeviceIdentifier generateTestIdentifier(int32_t id = DEVICE_ID) {
     InputDeviceIdentifier identifier;
     identifier.name = DEVICE_NAME + "_" + std::to_string(id);
     identifier.location = LOCATION;
@@ -69,8 +69,8 @@
                                        uint32_t sources = TOUCHSCREEN | STYLUS,
                                        bool isAlphabetic = false) {
     auto info = InputDeviceInfo();
-    info.initialize(id, /*generation=*/1, /*controllerNumber=*/1, getIdentifier(id), "alias",
-                    /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE);
+    info.initialize(id, /*generation=*/1, /*controllerNumber=*/1, generateTestIdentifier(id),
+                    "alias", /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE);
     info.addSource(sources);
     info.setKeyboardType(isAlphabetic ? AINPUT_KEYBOARD_TYPE_ALPHABETIC
                                       : AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
@@ -81,6 +81,8 @@
         generateTestDeviceInfo(DEVICE_ID, KEY_SOURCES, /*isAlphabetic=*/true);
 const InputDeviceInfo NON_ALPHABETIC_KEYBOARD_INFO =
         generateTestDeviceInfo(DEVICE_ID, KEY_SOURCES, /*isAlphabetic=*/false);
+const InputDeviceInfo TOUCHSCREEN_STYLUS_INFO = generateTestDeviceInfo(DEVICE_ID);
+const InputDeviceInfo SECOND_TOUCHSCREEN_STYLUS_INFO = generateTestDeviceInfo(DEVICE_ID_2);
 
 std::set<gui::Uid> uids(std::initializer_list<int32_t> vals) {
     std::set<gui::Uid> set;
@@ -351,12 +353,12 @@
     TestInputListener mTestListener;
     InputDeviceMetricsCollector mMetricsCollector{mTestListener, *this, USAGE_TIMEOUT};
 
-    void assertUsageLogged(InputDeviceIdentifier identifier, nanoseconds duration,
+    void assertUsageLogged(const InputDeviceInfo& info, nanoseconds duration,
                            std::optional<SourceUsageBreakdown> sourceBreakdown = {},
                            std::optional<UidUsageBreakdown> uidBreakdown = {}) {
         ASSERT_GE(mLoggedUsageSessions.size(), 1u);
-        const auto& [loggedIdentifier, report] = *mLoggedUsageSessions.begin();
-        ASSERT_EQ(identifier, loggedIdentifier);
+        const auto& [loggedInfo, report] = *mLoggedUsageSessions.begin();
+        ASSERT_EQ(info.getIdentifier(), loggedInfo.getIdentifier());
         ASSERT_EQ(duration, report.usageDuration);
         if (sourceBreakdown) {
             ASSERT_EQ(sourceBreakdown, report.sourceBreakdown);
@@ -387,14 +389,14 @@
     }
 
 private:
-    std::vector<std::tuple<InputDeviceIdentifier, DeviceUsageReport>> mLoggedUsageSessions;
+    std::vector<std::tuple<InputDeviceInfo, DeviceUsageReport>> mLoggedUsageSessions;
     nanoseconds mCurrentTime{TIME};
 
     nanoseconds getCurrentTime() override { return mCurrentTime; }
 
-    void logInputDeviceUsageReported(const InputDeviceIdentifier& identifier,
+    void logInputDeviceUsageReported(const InputDeviceInfo& info,
                                      const DeviceUsageReport& report) override {
-        mLoggedUsageSessions.emplace_back(identifier, report);
+        mLoggedUsageSessions.emplace_back(info, report);
     }
 };
 
@@ -438,7 +440,7 @@
 }
 
 TEST_F(InputDeviceMetricsCollectorTest, LogsSingleEventUsageSession) {
-    mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+    mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}});
 
     // Device was used.
     mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
@@ -448,11 +450,11 @@
     setCurrentTime(TIME + USAGE_TIMEOUT);
     mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
     // The usage session has zero duration because it consisted of only one event.
-    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(), 0ns));
+    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 0ns));
 }
 
 TEST_F(InputDeviceMetricsCollectorTest, LogsMultipleEventUsageSession) {
-    mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+    mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}});
 
     // Device was used.
     mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
@@ -468,11 +470,11 @@
     // Device was used again after the usage timeout.
     setCurrentTime(TIME + 42ns + 2 * USAGE_TIMEOUT);
     mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
-    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(), 42ns));
+    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 42ns));
 }
 
 TEST_F(InputDeviceMetricsCollectorTest, RemovingDeviceEndsUsageSession) {
-    mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+    mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}});
 
     // Device was used.
     mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
@@ -485,12 +487,12 @@
     // The device was removed before the usage timeout expired.
     setCurrentTime(TIME + 42ns);
     mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {}});
-    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(), 21ns));
+    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 21ns));
 }
 
 TEST_F(InputDeviceMetricsCollectorTest, TracksUsageFromDifferentDevicesIndependently) {
     mMetricsCollector.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(), generateTestDeviceInfo(DEVICE_ID_2)}});
+            {/*id=*/0, {TOUCHSCREEN_STYLUS_INFO, SECOND_TOUCHSCREEN_STYLUS_INFO}});
 
     // Device 1 was used.
     setCurrentTime(TIME);
@@ -509,7 +511,7 @@
     // Device 1 was used after its usage timeout expired. Its usage session is reported.
     setCurrentTime(TIME + 300ns + USAGE_TIMEOUT);
     mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
-    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(DEVICE_ID), 100ns));
+    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 100ns));
 
     // Device 2 was used.
     setCurrentTime(TIME + 350ns + USAGE_TIMEOUT);
@@ -525,13 +527,14 @@
     setCurrentTime(TIME + 400ns + (2 * USAGE_TIMEOUT));
     mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
     // Since Device 2's usage session ended, its usage should be reported.
-    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(DEVICE_ID_2), 150ns + USAGE_TIMEOUT));
+    ASSERT_NO_FATAL_FAILURE(
+            assertUsageLogged(SECOND_TOUCHSCREEN_STYLUS_INFO, 150ns + USAGE_TIMEOUT));
 
     ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
 }
 
 TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageBySource) {
-    mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+    mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}});
     InputDeviceMetricsLogger::SourceUsageBreakdown expectedSourceBreakdown;
 
     // Use touchscreen.
@@ -576,7 +579,7 @@
                                          100ns + USAGE_TIMEOUT);
     // Verify that only one usage session was logged for the device, and that session was broken
     // down by source correctly.
-    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(),
+    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO,
                                               400ns + USAGE_TIMEOUT + USAGE_TIMEOUT,
                                               expectedSourceBreakdown));
 
@@ -585,7 +588,7 @@
 
 TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageBySource_TrackSourceByDevice) {
     mMetricsCollector.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID), generateTestDeviceInfo(DEVICE_ID_2)}});
+            {/*id=*/0, {TOUCHSCREEN_STYLUS_INFO, SECOND_TOUCHSCREEN_STYLUS_INFO}});
     InputDeviceMetricsLogger::SourceUsageBreakdown expectedSourceBreakdown1;
     InputDeviceMetricsLogger::SourceUsageBreakdown expectedSourceBreakdown2;
 
@@ -602,15 +605,15 @@
     expectedSourceBreakdown1.emplace_back(InputDeviceUsageSource::TOUCHSCREEN, 100ns);
     expectedSourceBreakdown2.emplace_back(InputDeviceUsageSource::STYLUS_DIRECT, 100ns);
     ASSERT_NO_FATAL_FAILURE(
-            assertUsageLogged(getIdentifier(DEVICE_ID), 100ns, expectedSourceBreakdown1));
+            assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 100ns, expectedSourceBreakdown1));
     ASSERT_NO_FATAL_FAILURE(
-            assertUsageLogged(getIdentifier(DEVICE_ID_2), 100ns, expectedSourceBreakdown2));
+            assertUsageLogged(SECOND_TOUCHSCREEN_STYLUS_INFO, 100ns, expectedSourceBreakdown2));
 
     ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
 }
 
 TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageBySource_MultiSourceEvent) {
-    mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo(DEVICE_ID)}});
+    mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}});
     InputDeviceMetricsLogger::SourceUsageBreakdown expectedSourceBreakdown;
 
     mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN | STYLUS, //
@@ -634,13 +637,13 @@
     expectedSourceBreakdown.emplace_back(InputDeviceUsageSource::STYLUS_DIRECT, 200ns);
     expectedSourceBreakdown.emplace_back(InputDeviceUsageSource::TOUCHSCREEN, 300ns);
     ASSERT_NO_FATAL_FAILURE(
-            assertUsageLogged(getIdentifier(DEVICE_ID), 400ns, expectedSourceBreakdown));
+            assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 400ns, expectedSourceBreakdown));
 
     ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
 }
 
 TEST_F(InputDeviceMetricsCollectorTest, UidsNotTrackedWhenThereIsNoActiveSession) {
-    mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+    mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}});
 
     // Notify interaction with UIDs before the device is used.
     mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1}));
@@ -665,14 +668,15 @@
 
     // The first usage session is logged.
     static const UidUsageBreakdown emptyBreakdown;
-    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(), 100ns, /*sourceBreakdown=*/{},
+    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 100ns,
+                                              /*sourceBreakdown=*/{},
                                               /*uidBreakdown=*/emptyBreakdown));
 
     ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
 }
 
 TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageByUid) {
-    mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+    mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}});
     UidUsageBreakdown expectedUidBreakdown;
 
     mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
@@ -691,14 +695,14 @@
 
     // Remove the device to force the usage session to be logged.
     mMetricsCollector.notifyInputDevicesChanged({});
-    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(), 200ns, /*sourceBreakdown=*/{},
-                                              expectedUidBreakdown));
+    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 200ns,
+                                              /*sourceBreakdown=*/{}, expectedUidBreakdown));
 
     ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
 }
 
 TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageByUid_TracksMultipleSessionsForUid) {
-    mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+    mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}});
     UidUsageBreakdown expectedUidBreakdown;
 
     mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
@@ -742,7 +746,7 @@
     expectedUidBreakdown.emplace_back(2, 0ns);
     expectedUidBreakdown.emplace_back(3, 100ns);
     expectedUidBreakdown.emplace_back(4, 100ns);
-    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(), 500ns + USAGE_TIMEOUT,
+    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 500ns + USAGE_TIMEOUT,
                                               /*sourceBreakdown=*/{}, expectedUidBreakdown));
 
     ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
@@ -750,7 +754,7 @@
 
 TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageByUid_TracksUidsByDevice) {
     mMetricsCollector.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID), generateTestDeviceInfo(DEVICE_ID_2)}});
+            {/*id=*/0, {TOUCHSCREEN_STYLUS_INFO, SECOND_TOUCHSCREEN_STYLUS_INFO}});
     UidUsageBreakdown expectedUidBreakdown1;
     UidUsageBreakdown expectedUidBreakdown2;
 
@@ -773,9 +777,9 @@
     expectedUidBreakdown2.emplace_back(1, 100ns);
     expectedUidBreakdown2.emplace_back(3, 100ns);
     mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
-    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(DEVICE_ID), 200ns,
+    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 200ns,
                                               /*sourceBreakdown=*/{}, expectedUidBreakdown1));
-    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(DEVICE_ID_2), 100ns,
+    ASSERT_NO_FATAL_FAILURE(assertUsageLogged(SECOND_TOUCHSCREEN_STYLUS_INFO, 100ns,
                                               /*sourceBreakdown=*/{}, expectedUidBreakdown2));
 
     ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 443b037..38970f2 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -3349,6 +3349,31 @@
 }
 
 /**
+ * Test that invalid HOVER events sent by accessibility do not cause a fatal crash.
+ */
+TEST_F(InputDispatcherTest, InvalidA11yHoverStreamDoesNotCrash) {
+    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, 1200, 800));
+
+    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    MotionEventBuilder hoverEnterBuilder =
+            MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
+                    .addFlag(AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT);
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher, hoverEnterBuilder.build()));
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher, hoverEnterBuilder.build()));
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+}
+
+/**
  * If mouse is hovering when the touch goes down, the hovering should be stopped via HOVER_EXIT.
  */
 TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) {
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index bce0937..6539593 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -91,6 +91,9 @@
 static constexpr int32_t ACTION_POINTER_1_UP =
         AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
+static constexpr uint32_t STYLUS_FUSION_SOURCE =
+        AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_BLUETOOTH_STYLUS;
+
 // Minimum timestamp separation between subsequent input events from a Bluetooth device.
 static constexpr nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4);
 // Maximum smoothing time delta so that we don't generate events too far into the future.
@@ -2308,6 +2311,22 @@
 // ongoing stylus gesture that is being emitted by the touchscreen.
 using ExternalStylusIntegrationTest = BaseTouchIntegrationTest;
 
+TEST_F(ExternalStylusIntegrationTest, ExternalStylusConnectionChangesTouchscreenSource) {
+    // Create an external stylus capable of reporting pressure data that
+    // should be fused with a touch pointer.
+    std::unique_ptr<UinputExternalStylusWithPressure> stylus =
+            createUinputDevice<UinputExternalStylusWithPressure>();
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+    const auto stylusInfo = findDeviceByName(stylus->getName());
+    ASSERT_TRUE(stylusInfo);
+
+    // Connecting an external stylus changes the source of the touchscreen.
+    const auto deviceInfo = findDeviceByName(mDevice->getName());
+    ASSERT_TRUE(deviceInfo);
+    ASSERT_TRUE(isFromSource(deviceInfo->getSources(), STYLUS_FUSION_SOURCE));
+}
+
 TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureReported) {
     const Point centerPoint = mDevice->getCenterPoint();
 
@@ -2337,17 +2356,17 @@
     mDevice->sendDown(centerPoint);
     mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                  WithToolType(ToolType::STYLUS), WithButtonState(0),
-                  WithDeviceId(touchscreenId), WithPressure(100.f / RAW_PRESSURE_MAX))));
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithToolType(ToolType::STYLUS),
+                  WithButtonState(0), WithSource(STYLUS_FUSION_SOURCE), WithDeviceId(touchscreenId),
+                  WithPressure(100.f / RAW_PRESSURE_MAX))));
 
     // Change the pressure on the external stylus, and ensure the touchscreen generates a MOVE
     // event with the updated pressure.
     stylus->setPressure(200);
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                  WithToolType(ToolType::STYLUS), WithButtonState(0),
-                  WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX))));
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithToolType(ToolType::STYLUS),
+                  WithButtonState(0), WithSource(STYLUS_FUSION_SOURCE), WithDeviceId(touchscreenId),
+                  WithPressure(200.f / RAW_PRESSURE_MAX))));
 
     // The external stylus did not generate any events.
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
@@ -2392,8 +2411,8 @@
     // it shows up as a finger pointer.
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                  WithToolType(ToolType::FINGER), WithDeviceId(touchscreenId),
-                  WithPressure(1.f))));
+                  WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
+                  WithToolType(ToolType::FINGER), WithDeviceId(touchscreenId), WithPressure(1.f))));
 
     // Change the pressure on the external stylus. Since the pressure was not present at the start
     // of the gesture, it is ignored for now.
@@ -2405,6 +2424,7 @@
     mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                  WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
                   WithToolType(ToolType::FINGER))));
 
     // Start a new gesture. Since we have a valid pressure value, it shows up as a stylus.
@@ -2413,9 +2433,9 @@
     mDevice->sendDown(centerPoint);
     mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                  WithToolType(ToolType::STYLUS), WithButtonState(0),
-                  WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX))));
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithSource(STYLUS_FUSION_SOURCE),
+                  WithToolType(ToolType::STYLUS), WithButtonState(0), WithDeviceId(touchscreenId),
+                  WithPressure(200.f / RAW_PRESSURE_MAX))));
 
     // The external stylus did not generate any events.
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
@@ -2447,14 +2467,15 @@
             std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT));
     mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(
-            mTestListener
-                    ->assertNotifyMotionWasCalled(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                                        WithToolType(
-                                                                ToolType::FINGER),
-                                                        WithButtonState(0),
-                                                        WithDeviceId(touchscreenId),
-                                                        WithPressure(1.f)),
-                                                  waitUntil));
+            mTestListener->assertNotifyMotionWasCalled(AllOf(WithMotionAction(
+                                                                     AMOTION_EVENT_ACTION_DOWN),
+                                                             WithToolType(ToolType::FINGER),
+                                                             WithSource(AINPUT_SOURCE_TOUCHSCREEN |
+                                                                        AINPUT_SOURCE_STYLUS),
+                                                             WithButtonState(0),
+                                                             WithDeviceId(touchscreenId),
+                                                             WithPressure(1.f)),
+                                                       waitUntil));
 
     // The external stylus did not generate any events.
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
@@ -7567,12 +7588,10 @@
 
 protected:
     StylusState mStylusState{};
-    static constexpr uint32_t EXPECTED_SOURCE =
-            AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_BLUETOOTH_STYLUS;
 
     void testStartFusedStylusGesture(SingleTouchInputMapper& mapper) {
         auto toolTypeSource =
-                AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS));
+                AllOf(WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS));
 
         // The first pointer is withheld.
         processDown(mapper, 100, 200);
@@ -7606,7 +7625,7 @@
         processUp(mapper);
         processSync(mapper);
         ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(EXPECTED_SOURCE),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(STYLUS_FUSION_SOURCE),
                       WithToolType(ToolType::STYLUS))));
 
         mStylusState.pressure = 0.f;
@@ -7616,8 +7635,10 @@
     }
 
     void testUnsuccessfulFusionGesture(SingleTouchInputMapper& mapper) {
+        // When stylus fusion is not successful, events should be reported with the original source.
+        // In this case, it is from a touchscreen.
         auto toolTypeSource =
-                AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::FINGER));
+                AllOf(WithSource(AINPUT_SOURCE_TOUCHSCREEN), WithToolType(ToolType::FINGER));
 
         // The first pointer is withheld when an external stylus is connected,
         // and a timeout is requested.
@@ -7657,7 +7678,7 @@
 
 TEST_F(ExternalStylusFusionTest, UsesBluetoothStylusSource) {
     SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
-    ASSERT_EQ(EXPECTED_SOURCE, mapper.getSources());
+    ASSERT_EQ(STYLUS_FUSION_SOURCE, mapper.getSources());
 }
 
 TEST_F(ExternalStylusFusionTest, UnsuccessfulFusion) {
@@ -7674,8 +7695,7 @@
 // before the touch is reported by the touchscreen.
 TEST_F(ExternalStylusFusionTest, SuccessfulFusion_PressureFirst) {
     SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
-    auto toolTypeSource =
-            AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS));
+    auto toolTypeSource = AllOf(WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS));
 
     // The external stylus reports pressure first. It is ignored for now.
     mStylusState.pressure = 1.f;
@@ -7717,8 +7737,7 @@
 
 TEST_F(ExternalStylusFusionTest, FusedPointerReportsPressureChanges) {
     SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
-    auto toolTypeSource =
-            AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS));
+    auto toolTypeSource = AllOf(WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS));
 
     mStylusState.pressure = 0.8f;
     processExternalStylusState(mapper);
@@ -7779,7 +7798,7 @@
     processUp(mapper);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(EXPECTED_SOURCE),
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(STYLUS_FUSION_SOURCE),
                   WithToolType(ToolType::STYLUS))));
 
     ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
@@ -7788,7 +7807,7 @@
 
 TEST_F(ExternalStylusFusionTest, FusedPointerReportsToolTypeChanges) {
     SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
-    auto source = WithSource(EXPECTED_SOURCE);
+    auto source = WithSource(STYLUS_FUSION_SOURCE);
 
     mStylusState.pressure = 1.f;
     mStylusState.toolType = ToolType::ERASER;
@@ -7841,8 +7860,7 @@
 
 TEST_F(ExternalStylusFusionTest, FusedPointerReportsButtons) {
     SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
-    auto toolTypeSource =
-            AllOf(WithSource(EXPECTED_SOURCE), WithToolType(ToolType::STYLUS));
+    auto toolTypeSource = AllOf(WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS));
 
     ASSERT_NO_FATAL_FAILURE(testStartFusedStylusGesture(mapper));
 
diff --git a/services/inputflinger/tests/TimerProvider_test.cpp b/services/inputflinger/tests/TimerProvider_test.cpp
new file mode 100644
index 0000000..cb28823
--- /dev/null
+++ b/services/inputflinger/tests/TimerProvider_test.cpp
@@ -0,0 +1,311 @@
+/*
+ * Copyright 2023 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 "gestures/TimerProvider.h"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "InterfaceMocks.h"
+#include "TestConstants.h"
+#include "include/gestures.h"
+
+namespace android {
+
+namespace {
+
+class TestTimerProvider : public TimerProvider {
+public:
+    TestTimerProvider(InputReaderContext& context) : TimerProvider(context) {}
+
+    void setCurrentTime(nsecs_t time) { mCurrentTime = time; }
+
+protected:
+    nsecs_t getCurrentTime() override { return mCurrentTime; }
+
+private:
+    nsecs_t mCurrentTime = 0;
+};
+
+stime_t pushTimeOntoVector(stime_t triggerTime, void* data) {
+    std::vector<stime_t>* times = static_cast<std::vector<stime_t>*>(data);
+    times->push_back(triggerTime);
+    return NO_DEADLINE;
+}
+
+stime_t copyTimeToVariable(stime_t triggerTime, void* data) {
+    stime_t* time = static_cast<stime_t*>(data);
+    *time = triggerTime;
+    return NO_DEADLINE;
+}
+
+stime_t incrementInt(stime_t triggerTime, void* data) {
+    int* count = static_cast<int*>(data);
+    *count += 1;
+    return NO_DEADLINE;
+}
+
+} // namespace
+
+using testing::AtLeast;
+
+class TimerProviderTest : public testing::Test {
+public:
+    TimerProviderTest() : mProvider(mMockContext) {}
+
+protected:
+    void triggerCallbacksWithFakeTime(nsecs_t time) {
+        mProvider.setCurrentTime(time);
+        mProvider.triggerCallbacks(time);
+    }
+
+    MockInputReaderContext mMockContext;
+    TestTimerProvider mProvider;
+};
+
+TEST_F(TimerProviderTest, SingleDeadlineTriggersWhenTimeoutIsExactlyOnTime) {
+    GesturesTimer* timer = mProvider.createTimer();
+    std::vector<stime_t> callTimes;
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(3);
+
+    // Call through kGestureTimerProvider in this test case, so that we cover the stime_t to nsecs_t
+    // conversion code. This is why the delay is 1.0 rather than 1'000'000'000 here.
+    kGestureTimerProvider.set_fn(&mProvider, timer, 1.0, &pushTimeOntoVector, &callTimes);
+
+    triggerCallbacksWithFakeTime(900'000'000);
+    triggerCallbacksWithFakeTime(999'999'999);
+    EXPECT_EQ(0u, callTimes.size());
+    triggerCallbacksWithFakeTime(1'000'000'000);
+    ASSERT_EQ(1u, callTimes.size());
+    EXPECT_NEAR(1.0, callTimes[0], EPSILON);
+
+    // Now that the timer has triggered, it shouldn't trigger again if we get another timeout from
+    // InputReader.
+    triggerCallbacksWithFakeTime(1'300'000'000);
+    EXPECT_EQ(1u, callTimes.size());
+}
+
+TEST_F(TimerProviderTest, SingleDeadlineTriggersWhenTimeoutIsLate) {
+    GesturesTimer* timer = mProvider.createTimer();
+    stime_t callTime = -1.0;
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1);
+    mProvider.setDeadline(timer, 1'000'000'000, &copyTimeToVariable, &callTime);
+
+    triggerCallbacksWithFakeTime(1'010'000'000);
+    EXPECT_NEAR(1.01, callTime, EPSILON);
+}
+
+TEST_F(TimerProviderTest, SingleRescheduledDeadlineTriggers) {
+    GesturesTimer* timer = mProvider.createTimer();
+    std::vector<stime_t> callTimes;
+    auto callback = [](stime_t triggerTime, void* callbackData) {
+        std::vector<stime_t>* times = static_cast<std::vector<stime_t>*>(callbackData);
+        times->push_back(triggerTime);
+        if (times->size() < 2) {
+            return 1.0;
+        } else {
+            return NO_DEADLINE;
+        }
+    };
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1);
+    // The deadline should be rescheduled for 2.01s, since the first triggerCallbacks call is 0.01s
+    // late.
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(2'010'000'000)).Times(1);
+
+    mProvider.setDeadline(timer, 1'000'000'000, callback, &callTimes);
+
+    triggerCallbacksWithFakeTime(1'010'000'000);
+    ASSERT_EQ(1u, callTimes.size());
+    EXPECT_NEAR(1.01, callTimes[0], EPSILON);
+
+    triggerCallbacksWithFakeTime(2'020'000'000);
+    ASSERT_EQ(2u, callTimes.size());
+    EXPECT_NEAR(1.01, callTimes[0], EPSILON);
+    EXPECT_NEAR(2.02, callTimes[1], EPSILON);
+
+    triggerCallbacksWithFakeTime(3'000'000'000);
+    EXPECT_EQ(2u, callTimes.size());
+}
+
+TEST_F(TimerProviderTest, MultipleDeadlinesTriggerWithMultipleTimeouts) {
+    GesturesTimer* timer = mProvider.createTimer();
+    std::vector<stime_t> callTimes1;
+    std::vector<stime_t> callTimes2;
+
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(AtLeast(1));
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'500'000'000)).Times(1);
+
+    mProvider.setDeadline(timer, 1'000'000'000, &pushTimeOntoVector, &callTimes1);
+    mProvider.setDeadline(timer, 1'500'000'000, &pushTimeOntoVector, &callTimes2);
+
+    EXPECT_EQ(0u, callTimes1.size());
+    EXPECT_EQ(0u, callTimes2.size());
+
+    triggerCallbacksWithFakeTime(1'010'000'000);
+    ASSERT_EQ(1u, callTimes1.size());
+    EXPECT_NEAR(1.01, callTimes1[0], EPSILON);
+    EXPECT_EQ(0u, callTimes2.size());
+
+    triggerCallbacksWithFakeTime(1'500'000'000);
+    EXPECT_EQ(1u, callTimes1.size());
+    ASSERT_EQ(1u, callTimes2.size());
+    EXPECT_NEAR(1.5, callTimes2[0], EPSILON);
+}
+
+TEST_F(TimerProviderTest, MultipleDeadlinesTriggerWithOneLateTimeout) {
+    GesturesTimer* timer = mProvider.createTimer();
+    stime_t callTime1 = -1.0;
+    stime_t callTime2 = -1.0;
+
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(AtLeast(1));
+
+    mProvider.setDeadline(timer, 1'000'000'000, &copyTimeToVariable, &callTime1);
+    mProvider.setDeadline(timer, 1'500'000'000, &copyTimeToVariable, &callTime2);
+
+    triggerCallbacksWithFakeTime(1'510'000'000);
+    EXPECT_NEAR(1.51, callTime1, EPSILON);
+    EXPECT_NEAR(1.51, callTime2, EPSILON);
+}
+
+TEST_F(TimerProviderTest, MultipleDeadlinesAtSameTimeTriggerTogether) {
+    GesturesTimer* timer = mProvider.createTimer();
+    stime_t callTime1 = -1.0;
+    stime_t callTime2 = -1.0;
+
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(AtLeast(1));
+
+    mProvider.setDeadline(timer, 1'000'000'000, &copyTimeToVariable, &callTime1);
+    mProvider.setDeadline(timer, 1'000'000'000, &copyTimeToVariable, &callTime2);
+
+    triggerCallbacksWithFakeTime(1'000'000'000);
+    EXPECT_NEAR(1.0, callTime1, EPSILON);
+    EXPECT_NEAR(1.0, callTime2, EPSILON);
+}
+
+TEST_F(TimerProviderTest, MultipleTimersTriggerCorrectly) {
+    GesturesTimer* timer1 = mProvider.createTimer();
+    GesturesTimer* timer2 = mProvider.createTimer();
+    std::vector<stime_t> callTimes1;
+    std::vector<stime_t> callTimes2;
+
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(AtLeast(1));
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'250'000'000)).Times(1);
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'500'000'000)).Times(1);
+
+    mProvider.setDeadline(timer1, 500'000'000, &pushTimeOntoVector, &callTimes1);
+    mProvider.setDeadline(timer1, 1'250'000'000, &pushTimeOntoVector, &callTimes1);
+    mProvider.setDeadline(timer1, 1'500'000'000, &pushTimeOntoVector, &callTimes1);
+    mProvider.setDeadline(timer2, 750'000'000, &pushTimeOntoVector, &callTimes2);
+    mProvider.setDeadline(timer2, 1'250'000'000, &pushTimeOntoVector, &callTimes2);
+
+    triggerCallbacksWithFakeTime(800'000'000);
+    ASSERT_EQ(1u, callTimes1.size());
+    EXPECT_NEAR(0.8, callTimes1[0], EPSILON);
+    ASSERT_EQ(1u, callTimes2.size());
+    EXPECT_NEAR(0.8, callTimes2[0], EPSILON);
+
+    triggerCallbacksWithFakeTime(1'250'000'000);
+    ASSERT_EQ(2u, callTimes1.size());
+    EXPECT_NEAR(1.25, callTimes1[1], EPSILON);
+    ASSERT_EQ(2u, callTimes2.size());
+    EXPECT_NEAR(1.25, callTimes2[1], EPSILON);
+
+    triggerCallbacksWithFakeTime(1'501'000'000);
+    ASSERT_EQ(3u, callTimes1.size());
+    EXPECT_NEAR(1.501, callTimes1[2], EPSILON);
+    EXPECT_EQ(2u, callTimes2.size());
+}
+
+TEST_F(TimerProviderTest, CancelledTimerDoesntTrigger) {
+    GesturesTimer* timer = mProvider.createTimer();
+    int numCalls = 0;
+
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(AtLeast(1));
+    mProvider.setDeadline(timer, 500'000'000, &incrementInt, &numCalls);
+    mProvider.setDeadline(timer, 1'000'000'000, &incrementInt, &numCalls);
+    mProvider.cancelTimer(timer);
+
+    triggerCallbacksWithFakeTime(1'100'000'000);
+    EXPECT_EQ(0, numCalls);
+}
+
+TEST_F(TimerProviderTest, CancellingOneTimerDoesntAffectOthers) {
+    GesturesTimer* timer1 = mProvider.createTimer();
+    GesturesTimer* timer2 = mProvider.createTimer();
+    int numCalls1 = 0;
+    int numCalls2 = 0;
+
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(AtLeast(1));
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1);
+
+    mProvider.setDeadline(timer1, 500'000'000, &incrementInt, &numCalls1);
+    mProvider.setDeadline(timer2, 500'000'000, &incrementInt, &numCalls2);
+    mProvider.setDeadline(timer2, 1'000'000'000, &incrementInt, &numCalls2);
+    mProvider.cancelTimer(timer1);
+
+    triggerCallbacksWithFakeTime(501'000'000);
+    EXPECT_EQ(0, numCalls1);
+    EXPECT_EQ(1, numCalls2);
+
+    triggerCallbacksWithFakeTime(1'000'000'000);
+    EXPECT_EQ(0, numCalls1);
+    EXPECT_EQ(2, numCalls2);
+}
+
+TEST_F(TimerProviderTest, CancellingOneTimerCausesNewTimeoutRequestForAnother) {
+    GesturesTimer* timer1 = mProvider.createTimer();
+    GesturesTimer* timer2 = mProvider.createTimer();
+    auto callback = [](stime_t, void*) { return NO_DEADLINE; };
+
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(AtLeast(1));
+
+    mProvider.setDeadline(timer1, 500'000'000, callback, nullptr);
+    mProvider.setDeadline(timer2, 1'000'000'000, callback, nullptr);
+
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1);
+    mProvider.cancelTimer(timer1);
+}
+
+TEST_F(TimerProviderTest, CancelledTimerCanBeReused) {
+    GesturesTimer* timer = mProvider.createTimer();
+    int numCallsBeforeCancellation = 0;
+    int numCallsAfterCancellation = 0;
+
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(1);
+    EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1);
+
+    mProvider.setDeadline(timer, 500'000'000, &incrementInt, &numCallsBeforeCancellation);
+    mProvider.cancelTimer(timer);
+    mProvider.setDeadline(timer, 1'000'000'000, &incrementInt, &numCallsAfterCancellation);
+
+    triggerCallbacksWithFakeTime(1'000'000'000);
+    EXPECT_EQ(0, numCallsBeforeCancellation);
+    EXPECT_EQ(1, numCallsAfterCancellation);
+}
+
+TEST_F(TimerProviderTest, FreeingTimerCancelsFirst) {
+    GesturesTimer* timer = mProvider.createTimer();
+    int numCalls = 0;
+
+    mProvider.setDeadline(timer, 1'000'000'000, &incrementInt, &numCalls);
+    mProvider.freeTimer(timer);
+
+    triggerCallbacksWithFakeTime(1'000'000'000);
+    EXPECT_EQ(0, numCalls);
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/sensorservice/aidl/fuzzer/Android.bp b/services/sensorservice/aidl/fuzzer/Android.bp
index ed4829a..6870d4e 100644
--- a/services/sensorservice/aidl/fuzzer/Android.bp
+++ b/services/sensorservice/aidl/fuzzer/Android.bp
@@ -46,7 +46,6 @@
                 "unsigned-integer-overflow",
             ],
         },
-        address: true,
         integer_overflow: true,
     },
 
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 06c5e4c..2dc7332 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -148,18 +148,6 @@
         "libvulkan",
     ],
     sanitize: {
-        // By using the address sanitizer, we not only uncover any issues
-        // with the test, but also any issues with the code under test.
-        //
-        // Note: If you get an runtime link error like:
-        //
-        //   CANNOT LINK EXECUTABLE "/data/local/tmp/libcompositionengine_test": library "libclang_rt.asan-aarch64-android.so" not found
-        //
-        // it is because the address sanitizer shared objects are not installed
-        // by default in the system image.
-        //
-        // You can either "make dist tests" before flashing, or set this
-        // option to false temporarily.
-        address: true,
+        hwaddress: true,
     },
 }
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 22db247..fe56969 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -844,10 +844,16 @@
 
 bool OutputLayer::needsFiltering() const {
     const auto& state = getState();
-    const auto& displayFrame = state.displayFrame;
     const auto& sourceCrop = state.sourceCrop;
-    return sourceCrop.getHeight() != displayFrame.getHeight() ||
-            sourceCrop.getWidth() != displayFrame.getWidth();
+    auto displayFrameWidth = static_cast<float>(state.displayFrame.getWidth());
+    auto displayFrameHeight = static_cast<float>(state.displayFrame.getHeight());
+
+    if (state.bufferTransform & HAL_TRANSFORM_ROT_90) {
+        std::swap(displayFrameWidth, displayFrameHeight);
+    }
+
+    return sourceCrop.getHeight() != displayFrameHeight ||
+            sourceCrop.getWidth() != displayFrameWidth;
 }
 
 std::optional<LayerFE::LayerSettings> OutputLayer::getOverrideCompositionSettings() const {
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index 9039d16..630906a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -1614,5 +1614,20 @@
     EXPECT_TRUE(mOutputLayer.needsFiltering());
 }
 
+TEST_F(OutputLayerTest, needsFilteringReturnsFalseIfRotatedDisplaySizeSameAsSourceSize) {
+    mOutputLayer.editState().displayFrame = Rect(100, 100, 300, 200);
+    mOutputLayer.editState().sourceCrop = FloatRect{0.f, 0.f, 100.f, 200.f};
+    mOutputLayer.editState().bufferTransform = Hwc2::Transform::ROT_90;
+
+    EXPECT_FALSE(mOutputLayer.needsFiltering());
+}
+
+TEST_F(OutputLayerTest, needsFilteringReturnsTrueIfRotatedDisplaySizeDiffersFromSourceSize) {
+    mOutputLayer.editState().displayFrame = Rect(100, 100, 300, 200);
+    mOutputLayer.editState().sourceCrop = FloatRect{0.f, 0.f, 100.f, 200.f};
+
+    EXPECT_TRUE(mOutputLayer.needsFiltering());
+}
+
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 70ccaf8..1faf6a1 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -208,12 +208,12 @@
     return mPowerMode && *mPowerMode != hal::PowerMode::OFF;
 }
 
-void DisplayDevice::setActiveMode(DisplayModeId modeId, Fps displayFps, Fps renderFps) {
-    ATRACE_INT(mActiveModeFPSTrace.c_str(), displayFps.getIntValue());
+void DisplayDevice::setActiveMode(DisplayModeId modeId, Fps vsyncRate, Fps renderFps) {
+    ATRACE_INT(mActiveModeFPSTrace.c_str(), vsyncRate.getIntValue());
     ATRACE_INT(mRenderFrameRateFPSTrace.c_str(), renderFps.getIntValue());
 
     mRefreshRateSelector->setActiveMode(modeId, renderFps);
-    updateRefreshRateOverlayRate(displayFps, renderFps);
+    updateRefreshRateOverlayRate(vsyncRate, renderFps);
 }
 
 status_t DisplayDevice::initiateModeChange(const ActiveModeInfo& info,
@@ -230,14 +230,14 @@
     mIsModeSetPending = true;
 
     const auto& pendingMode = *info.modeOpt->modePtr;
-    ATRACE_INT(mActiveModeFPSHwcTrace.c_str(), pendingMode.getFps().getIntValue());
+    ATRACE_INT(mActiveModeFPSHwcTrace.c_str(), pendingMode.getVsyncRate().getIntValue());
 
     return mHwComposer.setActiveModeWithConstraints(getPhysicalId(), pendingMode.getHwcId(),
                                                     constraints, outTimeline);
 }
 
-void DisplayDevice::finalizeModeChange(DisplayModeId modeId, Fps displayFps, Fps renderFps) {
-    setActiveMode(modeId, displayFps, renderFps);
+void DisplayDevice::finalizeModeChange(DisplayModeId modeId, Fps vsyncRate, Fps renderFps) {
+    setActiveMode(modeId, vsyncRate, renderFps);
     mIsModeSetPending = false;
 }
 
@@ -253,7 +253,7 @@
         return vsyncPeriod;
     }
 
-    return refreshRateSelector().getActiveMode().modePtr->getVsyncPeriod();
+    return refreshRateSelector().getActiveMode().modePtr->getVsyncRate().getPeriodNsecs();
 }
 
 ui::Dataspace DisplayDevice::getCompositionDataSpace() const {
@@ -466,17 +466,23 @@
         features |= RefreshRateOverlay::Features::SetByHwc;
     }
 
+    // TODO(b/296636258) Update to use the render rate range in VRR mode.
     const auto fpsRange = mRefreshRateSelector->getSupportedRefreshRateRange();
     mRefreshRateOverlay = std::make_unique<RefreshRateOverlay>(fpsRange, features);
     mRefreshRateOverlay->setLayerStack(getLayerStack());
     mRefreshRateOverlay->setViewport(getSize());
-    updateRefreshRateOverlayRate(getActiveMode().modePtr->getFps(), getActiveMode().fps, setByHwc);
+    updateRefreshRateOverlayRate(getActiveMode().modePtr->getVsyncRate(), getActiveMode().fps,
+                                 setByHwc);
 }
 
-void DisplayDevice::updateRefreshRateOverlayRate(Fps displayFps, Fps renderFps, bool setByHwc) {
+void DisplayDevice::updateRefreshRateOverlayRate(Fps vsyncRate, Fps renderFps, bool setByHwc) {
     ATRACE_CALL();
-    if (mRefreshRateOverlay && (!mRefreshRateOverlay->isSetByHwc() || setByHwc)) {
-        mRefreshRateOverlay->changeRefreshRate(displayFps, renderFps);
+    if (mRefreshRateOverlay) {
+        if (!mRefreshRateOverlay->isSetByHwc() || setByHwc) {
+            mRefreshRateOverlay->changeRefreshRate(vsyncRate, renderFps);
+        } else {
+            mRefreshRateOverlay->changeRenderRate(renderFps);
+        }
     }
 }
 
@@ -486,7 +492,7 @@
         const auto newMode =
                 mRefreshRateSelector->onKernelTimerChanged(desiredModeId, timerExpired);
         if (newMode) {
-            updateRefreshRateOverlayRate(newMode->modePtr->getFps(), newMode->fps);
+            updateRefreshRateOverlayRate(newMode->modePtr->getVsyncRate(), newMode->fps);
             return true;
         }
     }
@@ -541,14 +547,14 @@
             return DesiredActiveModeAction::None;
         }
 
-        setActiveMode(desiredMode.getId(), desiredMode.getFps(), info.modeOpt->fps);
+        setActiveMode(desiredMode.getId(), desiredMode.getVsyncRate(), info.modeOpt->fps);
         return DesiredActiveModeAction::InitiateRenderRateSwitch;
     }
 
     // Set the render frame rate to the current physical refresh rate to schedule the next
     // frame as soon as possible.
-    setActiveMode(currentMode.modePtr->getId(), currentMode.modePtr->getFps(),
-                  currentMode.modePtr->getFps());
+    setActiveMode(currentMode.modePtr->getId(), currentMode.modePtr->getVsyncRate(),
+                  currentMode.modePtr->getVsyncRate());
 
     // Initiate a mode change.
     mDesiredActiveModeChanged = true;
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index a3fa701..a044534 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -224,14 +224,14 @@
         return mRefreshRateSelector->getActiveMode();
     }
 
-    void setActiveMode(DisplayModeId, Fps displayFps, Fps renderFps);
+    void setActiveMode(DisplayModeId, Fps vsyncRate, Fps renderFps);
 
     status_t initiateModeChange(const ActiveModeInfo&,
                                 const hal::VsyncPeriodChangeConstraints& constraints,
                                 hal::VsyncPeriodChangeTimeline* outTimeline)
             REQUIRES(kMainThreadContext);
 
-    void finalizeModeChange(DisplayModeId, Fps displayFps, Fps renderFps)
+    void finalizeModeChange(DisplayModeId, Fps vsyncRate, Fps renderFps)
             REQUIRES(kMainThreadContext);
 
     scheduler::RefreshRateSelector& refreshRateSelector() const { return *mRefreshRateSelector; }
@@ -246,7 +246,7 @@
     // Enables an overlay to be displayed with the current refresh rate
     void enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner, bool showRenderRate,
                                   bool showInMiddle) REQUIRES(kMainThreadContext);
-    void updateRefreshRateOverlayRate(Fps displayFps, Fps renderFps, bool setByHwc = false);
+    void updateRefreshRateOverlayRate(Fps vsyncRate, Fps renderFps, bool setByHwc = false);
     bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; }
     bool onKernelTimerChanged(std::optional<DisplayModeId>, bool timerExpired);
 
diff --git a/services/surfaceflinger/DisplayHardware/DisplayMode.h b/services/surfaceflinger/DisplayHardware/DisplayMode.h
index 1810925..1775a7a 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayMode.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayMode.h
@@ -31,6 +31,7 @@
 
 #include "DisplayHardware/Hal.h"
 #include "Scheduler/StrongTyping.h"
+#include "Utils/FlagUtils.h"
 
 namespace android {
 
@@ -49,6 +50,7 @@
 
 using DisplayModes = ftl::SmallMap<DisplayModeId, DisplayModePtr, 3>;
 using DisplayModeIterator = DisplayModes::const_iterator;
+using namespace com::android::graphics::surfaceflinger;
 
 class DisplayMode {
 public:
@@ -76,7 +78,12 @@
         }
 
         Builder& setVsyncPeriod(nsecs_t vsyncPeriod) {
-            mDisplayMode->mFps = Fps::fromPeriodNsecs(vsyncPeriod);
+            mDisplayMode->mVsyncRate = Fps::fromPeriodNsecs(vsyncPeriod);
+            return *this;
+        }
+
+        Builder& setVrrConfig(std::optional<hal::VrrConfig> vrrConfig) {
+            mDisplayMode->mVrrConfig = std::move(vrrConfig);
             return *this;
         }
 
@@ -130,8 +137,17 @@
     int32_t getWidth() const { return mResolution.getWidth(); }
     int32_t getHeight() const { return mResolution.getHeight(); }
 
-    Fps getFps() const { return mFps; }
-    nsecs_t getVsyncPeriod() const { return mFps.getPeriodNsecs(); }
+    // Peak refresh rate represents the highest refresh rate that can be used
+    // for the presentation.
+    Fps getPeakFps() const {
+        return flagutils::vrrConfigEnabled() && mVrrConfig
+                ? Fps::fromPeriodNsecs(mVrrConfig->minFrameIntervalNs)
+                : mVsyncRate;
+    }
+
+    Fps getVsyncRate() const { return mVsyncRate; }
+
+    std::optional<hal::VrrConfig> getVrrConfig() const { return mVrrConfig; }
 
     struct Dpi {
         float x = -1;
@@ -155,23 +171,25 @@
     PhysicalDisplayId mPhysicalDisplayId;
 
     ui::Size mResolution;
-    Fps mFps;
+    Fps mVsyncRate;
     Dpi mDpi;
     int32_t mGroup = -1;
+    std::optional<hal::VrrConfig> mVrrConfig;
 };
 
 inline bool equalsExceptDisplayModeId(const DisplayMode& lhs, const DisplayMode& rhs) {
     return lhs.getHwcId() == rhs.getHwcId() && lhs.getResolution() == rhs.getResolution() &&
-            lhs.getVsyncPeriod() == rhs.getVsyncPeriod() && lhs.getDpi() == rhs.getDpi() &&
-            lhs.getGroup() == rhs.getGroup();
+            lhs.getVsyncRate().getPeriodNsecs() == rhs.getVsyncRate().getPeriodNsecs() &&
+            lhs.getDpi() == rhs.getDpi() && lhs.getGroup() == rhs.getGroup();
 }
 
 inline std::string to_string(const DisplayMode& mode) {
-    return base::StringPrintf("{id=%d, hwcId=%d, resolution=%dx%d, refreshRate=%s, "
-                              "dpi=%.2fx%.2f, group=%d}",
+    return base::StringPrintf("{id=%d, hwcId=%d, resolution=%dx%d, vsyncRate=%s, "
+                              "dpi=%.2fx%.2f, group=%d, vrrConfig=%s}",
                               mode.getId().value(), mode.getHwcId(), mode.getWidth(),
-                              mode.getHeight(), to_string(mode.getFps()).c_str(), mode.getDpi().x,
-                              mode.getDpi().y, mode.getGroup());
+                              mode.getHeight(), to_string(mode.getVsyncRate()).c_str(),
+                              mode.getDpi().x, mode.getDpi().y, mode.getGroup(),
+                              to_string(mode.getVrrConfig()).c_str());
 }
 
 template <typename... DisplayModePtrs>
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index a9bb928..812621f 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -285,13 +285,12 @@
     std::vector<HWCDisplayMode> modes;
     modes.reserve(configs.size());
     for (auto config : configs) {
-        auto hwcMode = HWCDisplayMode{
-                .hwcId = static_cast<hal::HWConfigId>(config.configId),
-                .width = config.width,
-                .height = config.height,
-                .vsyncPeriod = config.vsyncPeriod,
-                .configGroup = config.configGroup,
-        };
+        auto hwcMode = HWCDisplayMode{.hwcId = static_cast<hal::HWConfigId>(config.configId),
+                                      .width = config.width,
+                                      .height = config.height,
+                                      .vsyncPeriod = config.vsyncPeriod,
+                                      .configGroup = config.configGroup,
+                                      .vrrConfig = config.vrrConfig};
 
         if (config.dpi) {
             hwcMode.dpiX = config.dpi->x;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 86f3825..b4d3d28 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -102,11 +102,13 @@
         float dpiX = -1.f;
         float dpiY = -1.f;
         int32_t configGroup = -1;
+        std::optional<hal::VrrConfig> vrrConfig;
 
         friend std::ostream& operator<<(std::ostream& os, const HWCDisplayMode& mode) {
             return os << "id=" << mode.hwcId << " res=" << mode.width << "x" << mode.height
                       << " vsyncPeriod=" << mode.vsyncPeriod << " dpi=" << mode.dpiX << "x"
-                      << mode.dpiY << " group=" << mode.configGroup;
+                      << mode.dpiY << " group=" << mode.configGroup
+                      << " vrrConfig=" << to_string(mode.vrrConfig).c_str();
         }
     };
 
diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h
index e95ae89..20f7548 100644
--- a/services/surfaceflinger/DisplayHardware/Hal.h
+++ b/services/surfaceflinger/DisplayHardware/Hal.h
@@ -24,6 +24,7 @@
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayConfiguration.h>
+#include <aidl/android/hardware/graphics/composer3/VrrConfig.h>
 
 #define ERROR_HAS_CHANGES 5
 
@@ -73,6 +74,7 @@
 using VsyncPeriodChangeConstraints = IComposerClient::VsyncPeriodChangeConstraints;
 using Hdr = aidl::android::hardware::graphics::common::Hdr;
 using DisplayConfiguration = V3_0::DisplayConfiguration;
+using VrrConfig = V3_0::VrrConfig;
 
 } // namespace hardware::graphics::composer::hal
 
@@ -148,6 +150,34 @@
     }
 }
 
+inline std::string to_string(
+        const std::optional<aidl::android::hardware::graphics::composer3::VrrConfig>& vrrConfig) {
+    if (vrrConfig) {
+        std::ostringstream out;
+        out << "{minFrameIntervalNs=" << vrrConfig->minFrameIntervalNs << ", ";
+        out << "frameIntervalPowerHints={";
+        if (vrrConfig->frameIntervalPowerHints) {
+            const auto& powerHint = *vrrConfig->frameIntervalPowerHints;
+            for (size_t i = 0; i < powerHint.size(); i++) {
+                if (i > 0) out << ", ";
+                out << "[frameIntervalNs=" << powerHint[i]->frameIntervalNs
+                    << ", averageRefreshPeriodNs=" << powerHint[i]->averageRefreshPeriodNs << "]";
+            }
+        }
+        out << "}, ";
+        out << "notifyExpectedPresentConfig={";
+        if (vrrConfig->notifyExpectedPresentConfig) {
+            out << "notifyExpectedPresentHeadsUpNs="
+                << vrrConfig->notifyExpectedPresentConfig->notifyExpectedPresentHeadsUpNs
+                << ", notifyExpectedPresentTimeoutNs="
+                << vrrConfig->notifyExpectedPresentConfig->notifyExpectedPresentTimeoutNs;
+        }
+        out << "}}";
+        return out.str();
+    }
+    return "N/A";
+}
+
 inline std::string to_string(hardware::graphics::composer::hal::V2_4::Error error) {
     // 5 is reserved for historical reason, during validation 5 means has changes.
     if (ERROR_HAS_CHANGES == static_cast<int32_t>(error)) {
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index 7537a39..a5e9368 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -85,6 +85,7 @@
     bool isTrustedOverlay;
     gui::GameMode gameMode;
     scheduler::LayerInfo::FrameRate frameRate;
+    scheduler::LayerInfo::FrameRateSelectionStrategy frameRateSelectionStrategy;
     ui::Transform::RotationFlags fixedTransformHint;
     std::optional<ui::Transform::RotationFlags> transformHint;
     bool handleSkipScreenshotFlag = false;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index da84e44..4c9fb06 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -813,11 +813,23 @@
                 RequestedLayerState::Changes::Hierarchy) ||
         snapshot.changes.any(RequestedLayerState::Changes::FrameRate |
                              RequestedLayerState::Changes::Hierarchy)) {
-        snapshot.frameRate = requested.requestedFrameRate.isValid() ? requested.requestedFrameRate
-                                                                    : parentSnapshot.frameRate;
+        bool shouldOverrideChildren = parentSnapshot.frameRateSelectionStrategy ==
+                scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren;
+        snapshot.frameRate = !requested.requestedFrameRate.isValid() || shouldOverrideChildren
+                ? parentSnapshot.frameRate
+                : requested.requestedFrameRate;
         snapshot.changes |= RequestedLayerState::Changes::FrameRate;
     }
 
+    if (forceUpdate || snapshot.clientChanges & layer_state_t::eFrameRateSelectionStrategyChanged) {
+        const auto strategy = scheduler::LayerInfo::convertFrameRateSelectionStrategy(
+                requested.frameRateSelectionStrategy);
+        snapshot.frameRateSelectionStrategy =
+                strategy == scheduler::LayerInfo::FrameRateSelectionStrategy::Self
+                ? parentSnapshot.frameRateSelectionStrategy
+                : strategy;
+    }
+
     if (forceUpdate || snapshot.clientChanges & layer_state_t::eFrameRateSelectionPriority) {
         snapshot.frameRateSelectionPriority =
                 (requested.frameRateSelectionPriority == Layer::PRIORITY_UNSET)
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 57ebee9..dc8694c 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -125,6 +125,8 @@
     defaultFrameRateCompatibility =
             static_cast<int8_t>(scheduler::LayerInfo::FrameRateCompatibility::Default);
     frameRateCategory = static_cast<int8_t>(FrameRateCategory::Default);
+    frameRateSelectionStrategy =
+            static_cast<int8_t>(scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
     dataspace = ui::Dataspace::V0_SRGB;
     gameMode = gui::GameMode::Unsupported;
     requestedFrameRate = {};
@@ -403,10 +405,19 @@
     return debug.str();
 }
 
+std::ostream& operator<<(std::ostream& out, const scheduler::LayerInfo::FrameRate& obj) {
+    out << obj.vote.rate;
+    out << " " << ftl::enum_string_full(obj.vote.type);
+    out << " " << ftl::enum_string_full(obj.category);
+    return out;
+}
+
 std::ostream& operator<<(std::ostream& out, const RequestedLayerState& obj) {
     out << obj.debugName;
     if (obj.relativeParentId != UNASSIGNED_LAYER_ID) out << " parent=" << obj.parentId;
     if (!obj.handleAlive) out << " handleNotAlive";
+    if (obj.requestedFrameRate.isValid())
+        out << " requestedFrameRate: {" << obj.requestedFrameRate << "}";
     return out;
 }
 
@@ -466,12 +477,18 @@
 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;
+    if (!bufferCrop.isEmpty() && externalTexture != nullptr) {
+        // if the buffer crop is defined and there's a valid buffer, intersect buffer size and crop
+        // since the crop should never exceed the size of the buffer.
+        Rect sizeAndCrop;
+        externalTexture->getBounds().intersect(bufferCrop, &sizeAndCrop);
+        return sizeAndCrop;
     } else if (externalTexture != nullptr) {
         // otherwise we use the whole buffer
         return externalTexture->getBounds();
+    } else if (!bufferCrop.isEmpty()) {
+        // if the buffer crop is defined, we use that
+        return bufferCrop;
     } else {
         // if we don't have a buffer yet, we use an empty/invalid crop
         return Rect();
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 2bbfa42..a73c511 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -192,6 +192,7 @@
     mDrawingState.dropInputMode = gui::DropInputMode::NONE;
     mDrawingState.dimmingEnabled = true;
     mDrawingState.defaultFrameRateCompatibility = FrameRateCompatibility::Default;
+    mDrawingState.frameRateSelectionStrategy = FrameRateSelectionStrategy::Self;
 
     if (args.flags & ISurfaceComposerClient::eNoColorFill) {
         // Set an invalid color so there is no color fill.
@@ -1251,9 +1252,14 @@
     return mBorderColor;
 }
 
-bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* transactionNeeded) {
-    // The frame rate for layer tree is this layer's frame rate if present, or the parent frame rate
+bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool overrideChildren,
+                                           bool* transactionNeeded) {
+    // Gets the frame rate to propagate to children.
     const auto frameRate = [&] {
+        if (overrideChildren && parentFrameRate.isValid()) {
+            return parentFrameRate;
+        }
+
         if (mDrawingState.frameRate.isValid()) {
             return mDrawingState.frameRate;
         }
@@ -1267,7 +1273,10 @@
     bool childrenHaveFrameRate = false;
     for (const sp<Layer>& child : mCurrentChildren) {
         childrenHaveFrameRate |=
-                child->propagateFrameRateForLayerTree(frameRate, transactionNeeded);
+                child->propagateFrameRateForLayerTree(frameRate,
+                                                      overrideChildren ||
+                                                              shouldOverrideChildrenFrameRate(),
+                                                      transactionNeeded);
     }
 
     // If we don't have a valid frame rate specification, but the children do, we set this
@@ -1300,7 +1309,7 @@
     }();
 
     bool transactionNeeded = false;
-    root->propagateFrameRateForLayerTree({}, &transactionNeeded);
+    root->propagateFrameRateForLayerTree({}, false, &transactionNeeded);
 
     // TODO(b/195668952): we probably don't need eTraversalNeeded here
     if (transactionNeeded) {
@@ -1338,6 +1347,17 @@
     return true;
 }
 
+bool Layer::setFrameRateSelectionStrategy(FrameRateSelectionStrategy strategy) {
+    if (mDrawingState.frameRateSelectionStrategy == strategy) return false;
+    mDrawingState.frameRateSelectionStrategy = strategy;
+    mDrawingState.sequence++;
+    mDrawingState.modified = true;
+
+    updateTreeHasFrameRateVote();
+    setTransactionFlags(eTransactionNeeded);
+    return true;
+}
+
 void Layer::setFrameTimelineVsyncForBufferTransaction(const FrameTimelineInfo& info,
                                                       nsecs_t postTime) {
     mDrawingState.postTime = postTime;
@@ -3092,6 +3112,32 @@
     return true;
 }
 
+void Layer::releasePreviousBuffer() {
+    mReleasePreviousBuffer = true;
+    if (!mBufferInfo.mBuffer ||
+        (!mDrawingState.buffer->hasSameBuffer(*mBufferInfo.mBuffer) ||
+         mDrawingState.frameNumber != mBufferInfo.mFrameNumber)) {
+        // If mDrawingState has a buffer, and we are about to update again
+        // before swapping to drawing state, then the first buffer will be
+        // dropped and we should decrement the pending buffer count and
+        // call any release buffer callbacks if set.
+        callReleaseBufferCallback(mDrawingState.releaseBufferListener,
+                                  mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber,
+                                  mDrawingState.acquireFence);
+        decrementPendingBufferCount();
+        if (mDrawingState.bufferSurfaceFrameTX != nullptr &&
+            mDrawingState.bufferSurfaceFrameTX->getPresentState() != PresentState::Presented) {
+            addSurfaceFrameDroppedForBuffer(mDrawingState.bufferSurfaceFrameTX, systemTime());
+            mDrawingState.bufferSurfaceFrameTX.reset();
+        }
+    } else if (EARLY_RELEASE_ENABLED && mLastClientCompositionFence != nullptr) {
+        callReleaseBufferCallback(mDrawingState.releaseBufferListener,
+                                  mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber,
+                                  mLastClientCompositionFence);
+        mLastClientCompositionFence = nullptr;
+    }
+}
+
 void Layer::resetDrawingStateBufferInfo() {
     mDrawingState.producerId = 0;
     mDrawingState.frameNumber = 0;
@@ -3116,29 +3162,7 @@
     ATRACE_FORMAT_INSTANT("setBuffer %s - %" PRIu64, getDebugName(), frameNumber);
 
     if (mDrawingState.buffer) {
-        mReleasePreviousBuffer = true;
-        if (!mBufferInfo.mBuffer ||
-            (!mDrawingState.buffer->hasSameBuffer(*mBufferInfo.mBuffer) ||
-             mDrawingState.frameNumber != mBufferInfo.mFrameNumber)) {
-            // If mDrawingState has a buffer, and we are about to update again
-            // before swapping to drawing state, then the first buffer will be
-            // dropped and we should decrement the pending buffer count and
-            // call any release buffer callbacks if set.
-            callReleaseBufferCallback(mDrawingState.releaseBufferListener,
-                                      mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber,
-                                      mDrawingState.acquireFence);
-            decrementPendingBufferCount();
-            if (mDrawingState.bufferSurfaceFrameTX != nullptr &&
-                mDrawingState.bufferSurfaceFrameTX->getPresentState() != PresentState::Presented) {
-                addSurfaceFrameDroppedForBuffer(mDrawingState.bufferSurfaceFrameTX, systemTime());
-                mDrawingState.bufferSurfaceFrameTX.reset();
-            }
-        } else if (EARLY_RELEASE_ENABLED && mLastClientCompositionFence != nullptr) {
-            callReleaseBufferCallback(mDrawingState.releaseBufferListener,
-                                      mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber,
-                                      mLastClientCompositionFence);
-            mLastClientCompositionFence = nullptr;
-        }
+        releasePreviousBuffer();
     } else if (buffer) {
         // if we are latching a buffer for the first time then clear the mLastLatchTime since
         // we don't want to incorrectly classify a frame if we miss the desired present time.
@@ -3156,6 +3180,12 @@
         mDrawingState.bufferSurfaceFrameTX = nullptr;
         setFrameTimelineVsyncForBufferlessTransaction(info, postTime);
         return true;
+    } else {
+        // release sideband stream if it exists and a non null buffer is being set
+        if (mDrawingState.sidebandStream != nullptr) {
+            mFlinger->mTunnelModeEnabledReporter->decrementTunnelModeCount();
+            mDrawingState.sidebandStream = nullptr;
+        }
     }
 
     if ((mDrawingState.producerId > bufferData.producerId) ||
@@ -3344,7 +3374,8 @@
     return true;
 }
 
-bool Layer::setSidebandStream(const sp<NativeHandle>& sidebandStream) {
+bool Layer::setSidebandStream(const sp<NativeHandle>& sidebandStream, const FrameTimelineInfo& info,
+                              nsecs_t postTime) {
     if (mDrawingState.sidebandStream == sidebandStream) return false;
 
     if (mDrawingState.sidebandStream != nullptr && sidebandStream == nullptr) {
@@ -3355,6 +3386,12 @@
 
     mDrawingState.sidebandStream = sidebandStream;
     mDrawingState.modified = true;
+    if (sidebandStream != nullptr && mDrawingState.buffer != nullptr) {
+        releasePreviousBuffer();
+        resetDrawingStateBufferInfo();
+        mDrawingState.bufferSurfaceFrameTX = nullptr;
+        setFrameTimelineVsyncForBufferlessTransaction(info, postTime);
+    }
     setTransactionFlags(eTransactionNeeded);
     if (!mSidebandStreamChanged.exchange(true)) {
         // mSidebandStreamChanged was false
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index dc4ceb0..40882f4 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -112,6 +112,7 @@
 
     using FrameRate = scheduler::LayerInfo::FrameRate;
     using FrameRateCompatibility = scheduler::LayerInfo::FrameRateCompatibility;
+    using FrameRateSelectionStrategy = scheduler::LayerInfo::FrameRateSelectionStrategy;
 
     struct State {
         int32_t z;
@@ -188,6 +189,8 @@
         // The combined frame rate of parents / children of this layer
         FrameRate frameRateForLayerTree;
 
+        FrameRateSelectionStrategy frameRateSelectionStrategy;
+
         // Set by window manager indicating the layer and all its children are
         // in a different orientation than the display. The hint suggests that
         // the graphic producers should receive a transform hint as if the
@@ -317,7 +320,8 @@
     bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/);
     bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/);
     bool setApi(int32_t /*api*/);
-    bool setSidebandStream(const sp<NativeHandle>& /*sidebandStream*/);
+    bool setSidebandStream(const sp<NativeHandle>& /*sidebandStream*/,
+                           const FrameTimelineInfo& /* info*/, nsecs_t /* postTime */);
     bool setTransactionCompletedListeners(const std::vector<sp<CallbackHandle>>& /*handles*/,
                                           bool willPresent);
     virtual bool setBackgroundColor(const half3& color, float alpha, ui::Dataspace dataspace)
@@ -781,6 +785,8 @@
     bool setFrameRate(FrameRate::FrameRateVote);
     bool setFrameRateCategory(FrameRateCategory);
 
+    bool setFrameRateSelectionStrategy(FrameRateSelectionStrategy);
+
     virtual void setFrameTimelineInfoForBuffer(const FrameTimelineInfo& /*info*/) {}
     void setFrameTimelineVsyncForBufferTransaction(const FrameTimelineInfo& info, nsecs_t postTime);
     void setFrameTimelineVsyncForBufferlessTransaction(const FrameTimelineInfo& info,
@@ -1096,7 +1102,8 @@
                                           const std::vector<Layer*>& layersInTree);
 
     void updateTreeHasFrameRateVote();
-    bool propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* transactionNeeded);
+    bool propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool overrideChildren,
+                                        bool* transactionNeeded);
     void setZOrderRelativeOf(const wp<Layer>& relativeOf);
     bool isTrustedOverlay() const;
     gui::DropInputMode getDropInputMode() const;
@@ -1158,6 +1165,11 @@
     void transferAvailableJankData(const std::deque<sp<CallbackHandle>>& handles,
                                    std::vector<JankData>& jankData);
 
+    bool shouldOverrideChildrenFrameRate() const {
+        return getDrawingState().frameRateSelectionStrategy ==
+                FrameRateSelectionStrategy::OverrideChildren;
+    }
+
     // Cached properties computed from drawing state
     // Effective transform taking into account parent transforms and any parent scaling, which is
     // a transform from the current layer coordinate space to display(screen) coordinate space.
@@ -1204,6 +1216,7 @@
     half4 mBorderColor;
 
     void setTransformHintLegacy(ui::Transform::RotationFlags);
+    void releasePreviousBuffer();
     void resetDrawingStateBufferInfo();
 
     // Transform hint provided to the producer. This must be accessed holding
diff --git a/services/surfaceflinger/OWNERS b/services/surfaceflinger/OWNERS
index 3270e4c..0aee7d4 100644
--- a/services/surfaceflinger/OWNERS
+++ b/services/surfaceflinger/OWNERS
@@ -4,6 +4,7 @@
 alecmouri@google.com
 chaviw@google.com
 domlaskowski@google.com
+jreck@google.com
 lpy@google.com
 pdwilliams@google.com
 racarr@google.com
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index 577211f..be04c09 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -19,6 +19,7 @@
 #include "Client.h"
 #include "Layer.h"
 #include "RefreshRateOverlay.h"
+#include "Utils/FlagUtils.h"
 
 #include <SkSurface.h>
 
@@ -27,7 +28,7 @@
 
 namespace android {
 
-auto RefreshRateOverlay::draw(int displayFps, int renderFps, SkColor color,
+auto RefreshRateOverlay::draw(int vsyncRate, int renderFps, SkColor color,
                               ui::Transform::RotationFlags rotation, ftl::Flags<Features> features)
         -> Buffers {
     const size_t loopCount = features.test(Features::Spinner) ? 6 : 1;
@@ -70,7 +71,7 @@
         canvas->setMatrix(canvasTransform);
 
         int left = 0;
-        drawNumber(displayFps, left, color, *canvas);
+        drawNumber(vsyncRate, left, color, *canvas);
         left += 3 * (kDigitWidth + kDigitSpace);
         if (features.test(Features::Spinner)) {
             switch (i) {
@@ -153,7 +154,7 @@
             .apply();
 }
 
-auto RefreshRateOverlay::getOrCreateBuffers(Fps displayFps, Fps renderFps) -> const Buffers& {
+auto RefreshRateOverlay::getOrCreateBuffers(Fps vsyncRate, Fps renderFps) -> const Buffers& {
     static const Buffers kNoBuffers;
     if (!mSurfaceControl) return kNoBuffers;
 
@@ -180,16 +181,16 @@
     createTransaction().setTransform(mSurfaceControl->get(), transform).apply();
 
     BufferCache::const_iterator it =
-            mBufferCache.find({displayFps.getIntValue(), renderFps.getIntValue(), transformHint});
+            mBufferCache.find({vsyncRate.getIntValue(), renderFps.getIntValue(), transformHint});
     if (it == mBufferCache.end()) {
         // HWC minFps is not known by the framework in order
         // to consider lower rates we set minFps to 0.
         const int minFps = isSetByHwc() ? 0 : mFpsRange.min.getIntValue();
         const int maxFps = mFpsRange.max.getIntValue();
 
-        // Clamp to the range. The current displayFps may be outside of this range if the display
+        // Clamp to the range. The current vsyncRate may be outside of this range if the display
         // has changed its set of supported refresh rates.
-        const int displayIntFps = std::clamp(displayFps.getIntValue(), minFps, maxFps);
+        const int displayIntFps = std::clamp(vsyncRate.getIntValue(), minFps, maxFps);
         const int renderIntFps = renderFps.getIntValue();
 
         // Ensure non-zero range to avoid division by zero.
@@ -242,17 +243,25 @@
     createTransaction().setLayerStack(mSurfaceControl->get(), stack).apply();
 }
 
-void RefreshRateOverlay::changeRefreshRate(Fps displayFps, Fps renderFps) {
-    mDisplayFps = displayFps;
+void RefreshRateOverlay::changeRefreshRate(Fps vsyncRate, Fps renderFps) {
+    mVsyncRate = vsyncRate;
     mRenderFps = renderFps;
-    const auto buffer = getOrCreateBuffers(displayFps, renderFps)[mFrame];
+    const auto buffer = getOrCreateBuffers(vsyncRate, renderFps)[mFrame];
     createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply();
 }
 
-void RefreshRateOverlay::animate() {
-    if (!mFeatures.test(Features::Spinner) || !mDisplayFps) return;
+void RefreshRateOverlay::changeRenderRate(Fps renderFps) {
+    if (mFeatures.test(Features::RenderRate) && mVsyncRate && flagutils::vrrConfigEnabled()) {
+        mRenderFps = renderFps;
+        const auto buffer = getOrCreateBuffers(*mVsyncRate, renderFps)[mFrame];
+        createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply();
+    }
+}
 
-    const auto& buffers = getOrCreateBuffers(*mDisplayFps, *mRenderFps);
+void RefreshRateOverlay::animate() {
+    if (!mFeatures.test(Features::Spinner) || !mVsyncRate) return;
+
+    const auto& buffers = getOrCreateBuffers(*mVsyncRate, *mRenderFps);
     mFrame = (mFrame + 1) % buffers.size();
     const auto buffer = buffers[mFrame];
     createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply();
diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h
index 65c61cb..ae334e5 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -50,13 +50,14 @@
     void setLayerStack(ui::LayerStack);
     void setViewport(ui::Size);
     void changeRefreshRate(Fps, Fps);
+    void changeRenderRate(Fps);
     void animate();
     bool isSetByHwc() const { return mFeatures.test(RefreshRateOverlay::Features::SetByHwc); }
 
 private:
     using Buffers = std::vector<sp<GraphicBuffer>>;
 
-    static Buffers draw(int displayFps, int renderFps, SkColor, ui::Transform::RotationFlags,
+    static Buffers draw(int vsyncRate, int renderFps, SkColor, ui::Transform::RotationFlags,
                         ftl::Flags<Features>);
     static void drawNumber(int number, int left, SkColor, SkCanvas&);
 
@@ -65,12 +66,12 @@
     SurfaceComposerClient::Transaction createTransaction() const;
 
     struct Key {
-        int displayFps;
+        int vsyncRate;
         int renderFps;
         ui::Transform::RotationFlags flags;
 
         bool operator==(Key other) const {
-            return displayFps == other.displayFps && renderFps == other.renderFps &&
+            return vsyncRate == other.vsyncRate && renderFps == other.renderFps &&
                     flags == other.flags;
         }
     };
@@ -78,7 +79,7 @@
     using BufferCache = ftl::SmallMap<Key, Buffers, 9>;
     BufferCache mBufferCache;
 
-    std::optional<Fps> mDisplayFps;
+    std::optional<Fps> mVsyncRate;
     std::optional<Fps> mRenderFps;
     size_t mFrame = 0;
 
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index c70ed2c..21714da 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -111,6 +111,15 @@
     return event;
 }
 
+DisplayEventReceiver::Event makeHotplugError(nsecs_t timestamp, int32_t connectionError) {
+    DisplayEventReceiver::Event event;
+    PhysicalDisplayId unusedDisplayId;
+    event.header = {DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG, unusedDisplayId, timestamp};
+    event.hotplug.connected = false;
+    event.hotplug.connectionError = connectionError;
+    return event;
+}
+
 DisplayEventReceiver::Event makeVSync(PhysicalDisplayId displayId, nsecs_t timestamp,
                                       uint32_t count, nsecs_t expectedPresentationTime,
                                       nsecs_t deadlineTimestamp) {
@@ -408,6 +417,13 @@
     mCondition.notify_all();
 }
 
+void EventThread::onHotplugConnectionError(int32_t errorCode) {
+    std::lock_guard<std::mutex> lock(mMutex);
+
+    mPendingEvents.push_back(makeHotplugError(systemTime(), errorCode));
+    mCondition.notify_all();
+}
+
 void EventThread::onModeChanged(const scheduler::FrameRateMode& mode) {
     std::lock_guard<std::mutex> lock(mMutex);
 
@@ -439,11 +455,15 @@
             mPendingEvents.pop_front();
 
             if (event->header.type == DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG) {
-                if (event->hotplug.connected && !mVSyncState) {
-                    mVSyncState.emplace(event->header.displayId);
-                } else if (!event->hotplug.connected && mVSyncState &&
-                           mVSyncState->displayId == event->header.displayId) {
-                    mVSyncState.reset();
+                if (event->hotplug.connectionError == 0) {
+                    if (event->hotplug.connected && !mVSyncState) {
+                        mVSyncState.emplace(event->header.displayId);
+                    } else if (!event->hotplug.connected && mVSyncState &&
+                               mVSyncState->displayId == event->header.displayId) {
+                        mVSyncState.reset();
+                    }
+                } else {
+                    // Ignore vsync stuff on an error.
                 }
             }
         }
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 7023445..576910e 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -111,6 +111,8 @@
 
     virtual void onHotplugReceived(PhysicalDisplayId displayId, bool connected) = 0;
 
+    virtual void onHotplugConnectionError(int32_t connectionError) = 0;
+
     // called when SF changes the active mode and apps needs to be notified about the change
     virtual void onModeChanged(const scheduler::FrameRateMode&) = 0;
 
@@ -159,6 +161,8 @@
 
     void onHotplugReceived(PhysicalDisplayId displayId, bool connected) override;
 
+    void onHotplugConnectionError(int32_t connectionError) override;
+
     void onModeChanged(const scheduler::FrameRateMode&) override;
 
     void onFrameRateOverridesChanged(PhysicalDisplayId displayId,
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 23eb31f..6b5327a 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -41,7 +41,7 @@
 
 bool isLayerActive(const LayerInfo& info, nsecs_t threshold) {
     // Layers with an explicit frame rate or frame rate category are always kept active,
-    // but ignore NoVote/NoPreference.
+    // but ignore NoVote.
     if (info.getSetFrameRateVote().isValid() && !info.getSetFrameRateVote().isNoVote()) {
         return true;
     }
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 551d744..875bdc8 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -116,12 +116,24 @@
         }
     }
 
+    // Vote the small dirty when a layer contains at least HISTORY_SIZE of small dirty updates.
+    bool isSmallDirty = false;
+    if (smallDirtyCount >= kNumSmallDirtyThreshold) {
+        if (mLastSmallDirtyCount >= HISTORY_SIZE) {
+            isSmallDirty = true;
+        } else {
+            mLastSmallDirtyCount++;
+        }
+    } else {
+        mLastSmallDirtyCount = 0;
+    }
+
     if (isFrequent || isInfrequent) {
         // If the layer was previously inconclusive, we clear
         // the history as indeterminate layers changed to frequent,
         // and we should not look at the stale data.
         return {isFrequent, isFrequent && !mIsFrequencyConclusive, /* isConclusive */ true,
-                /* isSmallDirty */ smallDirtyCount >= kNumSmallDirtyThreshold};
+                isSmallDirty};
     }
 
     // If we can't determine whether the layer is frequent or not, we return
@@ -292,6 +304,8 @@
 
     if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
         if (mLayerVote.category != FrameRateCategory::Default) {
+            ATRACE_FORMAT_INSTANT("ExplicitCategory (%s)",
+                                  ftl::enum_string(mLayerVote.category).c_str());
             ALOGV("%s uses frame rate category: %d", mName.c_str(),
                   static_cast<int>(mLayerVote.category));
             votes.push_back({LayerHistory::LayerVoteType::ExplicitCategory, Fps(),
@@ -300,6 +314,7 @@
 
         if (mLayerVote.fps.isValid() ||
             mLayerVote.type != LayerHistory::LayerVoteType::ExplicitDefault) {
+            ATRACE_FORMAT_INSTANT("Vote %s", ftl::enum_string(mLayerVote.type).c_str());
             ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));
             votes.push_back(mLayerVote);
         }
@@ -321,6 +336,7 @@
         ATRACE_FORMAT_INSTANT("infrequent");
         ALOGV("%s is infrequent", mName.c_str());
         mLastRefreshRate.infrequent = true;
+        mLastSmallDirtyCount = 0;
         // Infrequent layers vote for minimal refresh rate for
         // battery saving purposes and also to prevent b/135718869.
         votes.push_back({LayerHistory::LayerVoteType::Min, Fps()});
@@ -331,7 +347,7 @@
         clearHistory(now);
     }
 
-    // Return no vote if the latest frames are small dirty.
+    // Return no vote if the recent frames are small dirty.
     if (frequent.isSmallDirty && !mLastRefreshRate.reported.isValid()) {
         ATRACE_FORMAT_INSTANT("NoVote (small dirty)");
         ALOGV("%s is small dirty", mName.c_str());
@@ -341,11 +357,13 @@
 
     auto refreshRate = calculateRefreshRateIfPossible(selector, now);
     if (refreshRate.has_value()) {
+        ATRACE_FORMAT_INSTANT("calculated (%s)", to_string(*refreshRate).c_str());
         ALOGV("%s calculated refresh rate: %s", mName.c_str(), to_string(*refreshRate).c_str());
         votes.push_back({LayerHistory::LayerVoteType::Heuristic, refreshRate.value()});
         return votes;
     }
 
+    ATRACE_FORMAT_INSTANT("Max (can't resolve refresh rate)");
     ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());
     votes.push_back({LayerHistory::LayerVoteType::Max, Fps()});
     return votes;
@@ -485,9 +503,21 @@
     }
 }
 
+LayerInfo::FrameRateSelectionStrategy LayerInfo::convertFrameRateSelectionStrategy(
+        int8_t strategy) {
+    switch (strategy) {
+        case ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF:
+            return FrameRateSelectionStrategy::Self;
+        case ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN:
+            return FrameRateSelectionStrategy::OverrideChildren;
+        default:
+            LOG_ALWAYS_FATAL("Invalid frame rate selection strategy value %d", strategy);
+            return FrameRateSelectionStrategy::Self;
+    }
+}
+
 bool LayerInfo::FrameRate::isNoVote() const {
-    return vote.type == FrameRateCompatibility::NoVote ||
-            category == FrameRateCategory::NoPreference;
+    return vote.type == FrameRateCompatibility::NoVote;
 }
 
 bool LayerInfo::FrameRate::isValid() const {
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 1e08ec8..129b4c4 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -73,7 +73,7 @@
         FrameRateCategory category = FrameRateCategory::Default;
 
         // Returns true if the layer explicitly should contribute to frame rate scoring.
-        bool isNoVote() const { return RefreshRateSelector::isNoVote(type, category); }
+        bool isNoVote() const { return RefreshRateSelector::isNoVote(type); }
     };
 
     using RefreshRateVotes = ftl::SmallVector<LayerInfo::LayerVote, 2>;
@@ -96,6 +96,13 @@
         ftl_last = NoVote
     };
 
+    enum class FrameRateSelectionStrategy {
+        Self,
+        OverrideChildren,
+
+        ftl_last = OverrideChildren
+    };
+
     // Encapsulates the frame rate specifications of the layer. This information will be used
     // when the display refresh rate is determined.
     struct FrameRate {
@@ -139,11 +146,11 @@
         static FrameRateCompatibility convertCompatibility(int8_t compatibility);
 
         // Convert an ANATIVEWINDOW_CHANGE_FRAME_RATE_* value to a scheduler::Seamlessness.
-        // Logs fatal if the compatibility value is invalid.
+        // Logs fatal if the strategy value is invalid.
         static scheduler::Seamlessness convertChangeFrameRateStrategy(int8_t strategy);
 
         // Convert an ANATIVEWINDOW_FRAME_RATE_CATEGORY_* value to a FrameRateCategory.
-        // Logs fatal if the compatibility value is invalid.
+        // Logs fatal if the category value is invalid.
         static FrameRateCategory convertCategory(int8_t category);
 
         // True if the FrameRate has explicit frame rate specifications.
@@ -164,6 +171,10 @@
         }
     };
 
+    // Convert an ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_* value to FrameRateSelectionStrategy.
+    // Logs fatal if the strategy value is invalid.
+    static FrameRateSelectionStrategy convertFrameRateSelectionStrategy(int8_t strategy);
+
     static void setTraceEnabled(bool enabled) { sTraceEnabled = enabled; }
 
     LayerInfo(const std::string& name, uid_t ownerUid, LayerHistory::LayerVoteType defaultVote);
@@ -346,6 +357,10 @@
 
     RefreshRateHistory mRefreshRateHistory;
 
+    // This will be accessed from only one thread when counting a layer is frequent or infrequent,
+    // and to determine whether a layer is in small dirty updating.
+    mutable int32_t mLastSmallDirtyCount = 0;
+
     mutable std::unordered_map<LayerHistory::LayerVoteType, std::string> mTraceTags;
 
     // Shared for all LayerInfo instances
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index be28e98..b06723d 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -38,6 +38,9 @@
 
 #include "../SurfaceFlingerProperties.h"
 #include "RefreshRateSelector.h"
+#include "Utils/FlagUtils.h"
+
+#include <com_android_graphics_surfaceflinger_flags.h>
 
 #undef LOG_TAG
 #define LOG_TAG "RefreshRateSelector"
@@ -45,6 +48,8 @@
 namespace android::scheduler {
 namespace {
 
+using namespace com::android::graphics::surfaceflinger;
+
 struct RefreshRateScore {
     FrameRateMode frameRateMode;
     float overallScore;
@@ -69,7 +74,7 @@
 
     // Add all supported refresh rates.
     for (const auto& [id, mode] : modes) {
-        knownFrameRates.push_back(mode->getFps());
+        knownFrameRates.push_back(mode->getPeakFps());
     }
 
     // Sort and remove duplicates.
@@ -91,17 +96,17 @@
         const auto& mode1 = it1->second;
         const auto& mode2 = it2->second;
 
-        if (mode1->getVsyncPeriod() == mode2->getVsyncPeriod()) {
+        if (mode1->getVsyncRate().getPeriodNsecs() == mode2->getVsyncRate().getPeriodNsecs()) {
             return mode1->getGroup() > mode2->getGroup();
         }
 
-        return mode1->getVsyncPeriod() > mode2->getVsyncPeriod();
+        return mode1->getVsyncRate().getPeriodNsecs() > mode2->getVsyncRate().getPeriodNsecs();
     });
 
     return sortedModes;
 }
 
-std::pair<unsigned, unsigned> divisorRange(Fps fps, FpsRange range,
+std::pair<unsigned, unsigned> divisorRange(Fps vsyncRate, Fps peakFps, FpsRange range,
                                            RefreshRateSelector::Config::FrameRateOverride config) {
     if (config != RefreshRateSelector::Config::FrameRateOverride::Enabled) {
         return {1, 1};
@@ -109,8 +114,14 @@
 
     using fps_approx_ops::operator/;
     // use signed type as `fps / range.max` might be 0
-    const auto start = std::max(1, static_cast<int>(fps / range.max) - 1);
-    const auto end = fps /
+    auto start = std::max(1, static_cast<int>(peakFps / range.max) - 1);
+    if (flagutils::vrrConfigEnabled()) {
+        start = std::max(1,
+                         static_cast<int>(vsyncRate /
+                                          std::min(range.max, peakFps, fps_approx_ops::operator<)) -
+                                 1);
+    }
+    const auto end = vsyncRate /
             std::max(range.min, RefreshRateSelector::kMinSupportedFrameRate,
                      fps_approx_ops::operator<);
 
@@ -123,7 +134,8 @@
         for (const auto it2 : sortedModes) {
             const auto& mode2 = it2->second;
 
-            if (RefreshRateSelector::getFrameRateDivisor(mode1->getFps(), mode2->getFps()) >= 2) {
+            if (RefreshRateSelector::getFrameRateDivisor(mode1->getPeakFps(),
+                                                         mode2->getPeakFps()) >= 2) {
                 return true;
             }
         }
@@ -176,10 +188,12 @@
         if (!filterModes(*mode)) {
             continue;
         }
+        const auto vsyncRate = mode->getVsyncRate();
+        const auto peakFps = mode->getPeakFps();
         const auto [start, end] =
-                divisorRange(mode->getFps(), renderRange, mConfig.enableFrameRateOverride);
+                divisorRange(vsyncRate, peakFps, renderRange, mConfig.enableFrameRateOverride);
         for (auto divisor = start; divisor <= end; divisor++) {
-            const auto fps = mode->getFps() / divisor;
+            const auto fps = vsyncRate / divisor;
             using fps_approx_ops::operator<;
             if (divisor > 1 && fps < kMinSupportedFrameRate) {
                 break;
@@ -199,28 +213,30 @@
             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());
+                ALOGV("%s: including %s (%s(%s))", __func__, to_string(fps).c_str(),
+                      to_string(peakFps).c_str(), to_string(vsyncRate).c_str());
             } else {
                 // If the primary physical range is a single rate, prefer to stay in that rate
                 // even if there is a lower physical refresh rate available. This would cause more
                 // cases to stay within the primary physical range
-                const Fps existingModeFps = existingIter->second->second->getFps();
+                const Fps existingModeFps = existingIter->second->second->getPeakFps();
                 const bool existingModeIsPrimaryRange = policy.primaryRangeIsSingleRate() &&
                         policy.primaryRanges.physical.includes(existingModeFps);
                 const bool newModeIsPrimaryRange = policy.primaryRangeIsSingleRate() &&
-                        policy.primaryRanges.physical.includes(mode->getFps());
+                        policy.primaryRanges.physical.includes(mode->getPeakFps());
                 if (newModeIsPrimaryRange == existingModeIsPrimaryRange) {
                     // We might need to update the map as we found a lower refresh rate
-                    if (isStrictlyLess(mode->getFps(), existingModeFps)) {
+                    if (isStrictlyLess(mode->getPeakFps(), existingModeFps)) {
                         existingIter->second = it;
-                        ALOGV("%s: changing %s (%s) as we found a lower physical rate", __func__,
-                              to_string(fps).c_str(), to_string(mode->getFps()).c_str());
+                        ALOGV("%s: changing %s (%s(%s)) as we found a lower physical rate",
+                              __func__, to_string(fps).c_str(), to_string(peakFps).c_str(),
+                              to_string(vsyncRate).c_str());
                     }
                 } else if (newModeIsPrimaryRange) {
                     existingIter->second = it;
-                    ALOGV("%s: changing %s (%s) to stay in the primary range", __func__,
-                          to_string(fps).c_str(), to_string(mode->getFps()).c_str());
+                    ALOGV("%s: changing %s (%s(%s)) to stay in the primary range", __func__,
+                          to_string(fps).c_str(), to_string(peakFps).c_str(),
+                          to_string(vsyncRate).c_str());
                 }
             }
         }
@@ -237,8 +253,8 @@
     const auto lowestRefreshRateIt =
             std::min_element(frameRateModes.begin(), frameRateModes.end(),
                              [](const FrameRateMode& lhs, const FrameRateMode& rhs) {
-                                 return isStrictlyLess(lhs.modePtr->getFps(),
-                                                       rhs.modePtr->getFps());
+                                 return isStrictlyLess(lhs.modePtr->getVsyncRate(),
+                                                       rhs.modePtr->getVsyncRate());
                              });
     frameRateModes.erase(frameRateModes.begin(), lowestRefreshRateIt);
 
@@ -584,7 +600,8 @@
               layer.name.c_str(), ftl::enum_string(layer.vote).c_str(), layer.weight,
               layer.desiredRefreshRate.getValue(),
               ftl::enum_string(layer.frameRateCategory).c_str());
-        if (layer.isNoVote() || layer.vote == LayerVoteType::Min) {
+        if (layer.isNoVote() || layer.frameRateCategory == FrameRateCategory::NoPreference ||
+            layer.vote == LayerVoteType::Min) {
             continue;
         }
 
@@ -623,7 +640,7 @@
             }
 
             const bool inPrimaryPhysicalRange =
-                    policy->primaryRanges.physical.includes(modePtr->getFps());
+                    policy->primaryRanges.physical.includes(modePtr->getPeakFps());
             const bool inPrimaryRenderRange = policy->primaryRanges.render.includes(fps);
             if (((policy->primaryRangeIsSingleRate() && !inPrimaryPhysicalRange) ||
                  !inPrimaryRenderRange) &&
@@ -664,21 +681,24 @@
                             Fps::fromValue(mConfig.frameRateMultipleThreshold / 2);
             if (fixedSourceLayer && layerBelowThreshold) {
                 const bool modeAboveThreshold =
-                        modePtr->getFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold);
+                        modePtr->getPeakFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold);
                 if (modeAboveThreshold) {
-                    ALOGV("%s gives %s (%s) fixed source (above threshold) score of %.4f",
+                    ALOGV("%s gives %s (%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);
+                          to_string(modePtr->getPeakFps()).c_str(),
+                          to_string(modePtr->getVsyncRate()).c_str(), layerScore);
                     fixedRateBelowThresholdLayersScore.modeAboveThreshold += weightedLayerScore;
                 } else {
-                    ALOGV("%s gives %s (%s) fixed source (below threshold) score of %.4f",
+                    ALOGV("%s gives %s (%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);
+                          to_string(modePtr->getPeakFps()).c_str(),
+                          to_string(modePtr->getVsyncRate()).c_str(), layerScore);
                     fixedRateBelowThresholdLayersScore.modeBelowThreshold += weightedLayerScore;
                 }
             } else {
-                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);
+                ALOGV("%s gives %s (%s(%s)) score of %.4f", formatLayerInfo(layer, weight).c_str(),
+                      to_string(fps).c_str(), to_string(modePtr->getPeakFps()).c_str(),
+                      to_string(modePtr->getVsyncRate()).c_str(), layerScore);
                 overallScore += weightedLayerScore;
             }
         }
@@ -699,13 +719,14 @@
                                  [](RefreshRateScore max, RefreshRateScore current) {
                                      return current.overallScore > max.overallScore;
                                  });
-        ALOGV("%s (%s) is the best refresh rate without fixed source layers. It is %s the "
+        ALOGV("%s (%s(%s)) is the best refresh rate without fixed source layers. It is %s the "
               "threshold for "
               "refresh rate multiples",
               to_string(maxScoreIt->frameRateMode.fps).c_str(),
-              to_string(maxScoreIt->frameRateMode.modePtr->getFps()).c_str(),
+              to_string(maxScoreIt->frameRateMode.modePtr->getPeakFps()).c_str(),
+              to_string(maxScoreIt->frameRateMode.modePtr->getVsyncRate()).c_str(),
               maxScoreAboveThreshold ? "above" : "below");
-        return maxScoreIt->frameRateMode.modePtr->getFps() >=
+        return maxScoreIt->frameRateMode.modePtr->getPeakFps() >=
                 Fps::fromValue(mConfig.frameRateMultipleThreshold);
     }();
 
@@ -715,8 +736,9 @@
         if (maxScoreAboveThreshold) {
             overallScore += fixedRateBelowThresholdLayersScore.modeAboveThreshold;
         }
-        ALOGV("%s (%s) adjusted overallScore is %.4f", to_string(frameRateMode.fps).c_str(),
-              to_string(frameRateMode.modePtr->getFps()).c_str(), overallScore);
+        ALOGV("%s (%s(%s)) adjusted overallScore is %.4f", to_string(frameRateMode.fps).c_str(),
+              to_string(frameRateMode.modePtr->getPeakFps()).c_str(),
+              to_string(frameRateMode.modePtr->getVsyncRate()).c_str(), overallScore);
     }
 
     // Now that we scored all the refresh rates we need to pick the one that got the highest
@@ -842,6 +864,7 @@
 
     const auto* policyPtr = getCurrentPolicyLocked();
     // We don't want to run lower than 30fps
+    // TODO(b/297600226): revise this for dVRR
     const Fps minFrameRate = std::max(policyPtr->appRequestRanges.render.min, 30_Hz, isApproxLess);
 
     using fps_approx_ops::operator/;
@@ -883,7 +906,8 @@
         }
 
         for (const auto& layer : layersWithSameUid) {
-            if (layer->vote == LayerVoteType::NoVote || layer->vote == LayerVoteType::Min) {
+            if (layer->isNoVote() || layer->frameRateCategory == FrameRateCategory::NoPreference ||
+                layer->vote == LayerVoteType::Min) {
                 continue;
             }
 
@@ -933,7 +957,7 @@
     const auto current = [&]() REQUIRES(mLock) -> FrameRateMode {
         if (desiredActiveModeId) {
             const auto& modePtr = mDisplayModes.get(*desiredActiveModeId)->get();
-            return FrameRateMode{modePtr->getFps(), ftl::as_non_null(modePtr)};
+            return FrameRateMode{modePtr->getPeakFps(), ftl::as_non_null(modePtr)};
         }
 
         return getActiveModeLocked();
@@ -944,7 +968,7 @@
         return {};
     }
 
-    return timerExpired ? FrameRateMode{min->getFps(), ftl::as_non_null(min)} : current;
+    return timerExpired ? FrameRateMode{min->getPeakFps(), ftl::as_non_null(min)} : current;
 }
 
 const DisplayModePtr& RefreshRateSelector::getMinRefreshRateByPolicyLocked() const {
@@ -970,12 +994,12 @@
     bool maxByAnchorFound = false;
     for (auto it = mPrimaryFrameRates.rbegin(); it != mPrimaryFrameRates.rend(); ++it) {
         using namespace fps_approx_ops;
-        if (it->modePtr->getFps() > (*max)->getFps()) {
+        if (it->modePtr->getPeakFps() > (*max)->getPeakFps()) {
             max = &it->modePtr;
         }
 
         if (anchorGroup == it->modePtr->getGroup() &&
-            it->modePtr->getFps() >= (*maxByAnchor)->getFps()) {
+            it->modePtr->getPeakFps() >= (*maxByAnchor)->getPeakFps()) {
             maxByAnchorFound = true;
             maxByAnchor = &it->modePtr;
         }
@@ -1053,8 +1077,9 @@
             return;
         }
 
-        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);
+        ALOGV("%s(%s) %s (%s(%s)) scored %.2f", whence, ftl::enum_string(refreshRateOrder).c_str(),
+              to_string(frameRateMode.fps).c_str(), to_string(modePtr->getPeakFps()).c_str(),
+              to_string(modePtr->getVsyncRate()).c_str(), score);
         ranking.emplace_back(ScoredFrameRate{frameRateMode, score});
     };
 
@@ -1134,8 +1159,8 @@
     mDisplayModes = std::move(modes);
     const auto activeModeOpt = mDisplayModes.get(activeModeId);
     LOG_ALWAYS_FATAL_IF(!activeModeOpt);
-    mActiveModeOpt =
-            FrameRateMode{activeModeOpt->get()->getFps(), ftl::as_non_null(activeModeOpt->get())};
+    mActiveModeOpt = FrameRateMode{activeModeOpt->get()->getPeakFps(),
+                                   ftl::as_non_null(activeModeOpt->get())};
 
     const auto sortedModes = sortByRefreshRate(mDisplayModes);
     mMinRefreshRateModeIt = sortedModes.front();
@@ -1161,7 +1186,7 @@
     if (mConfig.enableFrameRateOverride ==
         Config::FrameRateOverride::AppOverrideNativeRefreshRates) {
         for (const auto& [_, mode] : mDisplayModes) {
-            mAppOverrideNativeRefreshRates.try_emplace(mode->getFps(), ftl::unit);
+            mAppOverrideNativeRefreshRates.try_emplace(mode->getPeakFps(), ftl::unit);
         }
     }
 
@@ -1171,7 +1196,7 @@
 bool RefreshRateSelector::isPolicyValidLocked(const Policy& policy) const {
     // defaultMode must be a valid mode, and within the given refresh rate range.
     if (const auto mode = mDisplayModes.get(policy.defaultMode)) {
-        if (!policy.primaryRanges.physical.includes(mode->get()->getFps())) {
+        if (!policy.primaryRanges.physical.includes(mode->get()->getPeakFps())) {
             ALOGE("Default mode is not in the primary range.");
             return false;
         }
@@ -1284,8 +1309,8 @@
             return mode.getResolution() == defaultMode->getResolution() &&
                     mode.getDpi() == defaultMode->getDpi() &&
                     (policy->allowGroupSwitching || mode.getGroup() == defaultMode->getGroup()) &&
-                    ranges.physical.includes(mode.getFps()) &&
-                    (supportsFrameRateOverride() || ranges.render.includes(mode.getFps()));
+                    ranges.physical.includes(mode.getPeakFps()) &&
+                    (supportsFrameRateOverride() || ranges.render.includes(mode.getPeakFps()));
         };
 
         auto frameRateModes = createFrameRateModes(*policy, filterModes, ranges.render);
@@ -1340,13 +1365,13 @@
 auto RefreshRateSelector::getIdleTimerAction() const -> KernelIdleTimerAction {
     std::lock_guard lock(mLock);
 
-    const Fps deviceMinFps = mMinRefreshRateModeIt->second->getFps();
+    const Fps deviceMinFps = mMinRefreshRateModeIt->second->getPeakFps();
     const DisplayModePtr& minByPolicy = getMinRefreshRateByPolicyLocked();
 
     // Kernel idle timer will set the refresh rate to the device min. If DisplayManager says that
     // the min allowed refresh rate is higher than the device min, we do not want to enable the
     // timer.
-    if (isStrictlyLess(deviceMinFps, minByPolicy->getFps())) {
+    if (isStrictlyLess(deviceMinFps, minByPolicy->getPeakFps())) {
         return KernelIdleTimerAction::TurnOff;
     }
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index 73e1d38..5d32414 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -187,14 +187,12 @@
 
         bool operator!=(const LayerRequirement& other) const { return !(*this == other); }
 
-        bool isNoVote() const { return RefreshRateSelector::isNoVote(vote, frameRateCategory); }
+        bool isNoVote() const { return RefreshRateSelector::isNoVote(vote); }
     };
 
     // Returns true if the layer explicitly instructs to not contribute to refresh rate selection.
     // In other words, true if the layer should be ignored.
-    static bool isNoVote(LayerVoteType vote, FrameRateCategory category) {
-        return vote == LayerVoteType::NoVote || category == FrameRateCategory::NoPreference;
-    }
+    static bool isNoVote(LayerVoteType vote) { return vote == LayerVoteType::NoVote; }
 
     // Global state describing signals that affect refresh rate choice.
     struct GlobalSignals {
@@ -253,7 +251,8 @@
 
     FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
         std::lock_guard lock(mLock);
-        return {mMinRefreshRateModeIt->second->getFps(), mMaxRefreshRateModeIt->second->getFps()};
+        return {mMinRefreshRateModeIt->second->getPeakFps(),
+                mMaxRefreshRateModeIt->second->getPeakFps()};
     }
 
     ftl::Optional<FrameRateMode> onKernelTimerChanged(
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 27c96f7..aa24f56 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -347,6 +347,17 @@
     thread->onHotplugReceived(displayId, connected);
 }
 
+void Scheduler::onHotplugConnectionError(ConnectionHandle handle, int32_t errorCode) {
+    android::EventThread* thread;
+    {
+        std::lock_guard<std::mutex> lock(mConnectionsLock);
+        RETURN_IF_INVALID_HANDLE(handle);
+        thread = mConnections[handle].thread.get();
+    }
+
+    thread->onHotplugConnectionError(errorCode);
+}
+
 void Scheduler::enableSyntheticVsync(bool enable) {
     // TODO(b/241285945): Remove connection handles.
     const ConnectionHandle handle = mAppConnectionHandle;
@@ -494,7 +505,7 @@
 
     if (display.schedulePtr->isHardwareVsyncAllowed(allowToEnable)) {
         if (!refreshRate) {
-            refreshRate = display.selectorPtr->getActiveMode().modePtr->getFps();
+            refreshRate = display.selectorPtr->getActiveMode().modePtr->getVsyncRate();
         }
         if (refreshRate->isValid()) {
             constexpr bool kForce = false;
@@ -542,7 +553,7 @@
                         to_string(mode.fps).c_str(), to_string(renderFrameRate).c_str(), id.value);
 
     ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(),
-          to_string(mode.modePtr->getFps()).c_str());
+          to_string(mode.modePtr->getVsyncRate()).c_str());
 
     display.schedulePtr->getTracker().setRenderRate(renderFrameRate);
 }
@@ -717,7 +728,7 @@
 
     // TODO(145561154): cleanup the kernel idle timer implementation and the refresh rate
     // magic number
-    const Fps refreshRate = pacesetterSelectorPtr()->getActiveMode().modePtr->getFps();
+    const Fps refreshRate = pacesetterSelectorPtr()->getActiveMode().modePtr->getPeakFps();
 
     constexpr Fps FPS_THRESHOLD_FOR_KERNEL_TIMER = 65_Hz;
     using namespace fps_approx_ops;
@@ -877,7 +888,7 @@
 
         newVsyncSchedulePtr = pacesetter.schedulePtr;
 
-        const Fps refreshRate = pacesetter.selectorPtr->getActiveMode().modePtr->getFps();
+        const Fps refreshRate = pacesetter.selectorPtr->getActiveMode().modePtr->getVsyncRate();
         constexpr bool kForce = true;
         newVsyncSchedulePtr->startPeriodTransition(refreshRate.getPeriod(), kForce);
     }
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index d65df2a..822f7cc 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -161,6 +161,8 @@
     sp<EventThreadConnection> getEventConnection(ConnectionHandle);
 
     void onHotplugReceived(ConnectionHandle, PhysicalDisplayId, bool connected);
+    void onHotplugConnectionError(ConnectionHandle, int32_t errorCode);
+
     void onPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&) EXCLUDES(mPolicyLock);
     void onNonPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&);
 
diff --git a/services/surfaceflinger/Scheduler/VsyncConfiguration.h b/services/surfaceflinger/Scheduler/VsyncConfiguration.h
index a24e43f..b6cb373 100644
--- a/services/surfaceflinger/Scheduler/VsyncConfiguration.h
+++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.h
@@ -143,7 +143,7 @@
  */
 class WorkDuration : public VsyncConfiguration {
 public:
-    explicit WorkDuration(Fps currentRefrshRate);
+    explicit WorkDuration(Fps currentRefreshRate);
 
 protected:
     // Used for unit tests
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
index db38ebe..59a6df2 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
@@ -36,7 +36,8 @@
 };
 
 inline std::string to_string(const FrameRateMode& mode) {
-    return to_string(mode.fps) + " (" + to_string(mode.modePtr->getFps()) + ")";
+    return to_string(mode.fps) + " (" + to_string(mode.modePtr->getPeakFps()) + "(" +
+            to_string(mode.modePtr->getVsyncRate()) + "))";
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index e6a2de5..b7d2f9a 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -155,6 +155,7 @@
 #include "TimeStats/TimeStats.h"
 #include "TunnelModeEnabledReporter.h"
 #include "Utils/Dumper.h"
+#include "Utils/FlagUtils.h"
 #include "WindowInfosListenerInvoker.h"
 
 #include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
@@ -1043,8 +1044,8 @@
         outMode.xDpi = xDpi;
         outMode.yDpi = yDpi;
 
-        const nsecs_t period = mode->getVsyncPeriod();
-        outMode.refreshRate = Fps::fromPeriodNsecs(period).getValue();
+        const auto peakFps = mode->getPeakFps();
+        outMode.refreshRate = peakFps.getValue();
 
         const auto vsyncConfigSet =
                 mVsyncConfiguration->getConfigsForRefreshRate(Fps::fromValue(outMode.refreshRate));
@@ -1064,7 +1065,7 @@
         //
         // We add an additional 1ms to allow for processing time and
         // differences between the ideal and actual refresh rate.
-        outMode.presentationDeadline = period - outMode.sfVsyncOffset + 1000000;
+        outMode.presentationDeadline = peakFps.getPeriodNsecs() - outMode.sfVsyncOffset + 1000000;
         excludeDolbyVisionIf4k30Present(display->getHdrCapabilities().getSupportedHdrTypes(),
                                         outMode);
         info->supportedDisplayModes.push_back(outMode);
@@ -1199,7 +1200,7 @@
             // Start receiving vsync samples now, so that we can detect a period
             // switch.
             mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */,
-                                              mode.modePtr->getFps());
+                                              mode.modePtr->getVsyncRate());
 
             // As we called to set period, we will call to onRefreshRateChangeCompleted once
             // VsyncController model is locked.
@@ -1253,7 +1254,7 @@
         const auto& snapshot = snapshotRef.get();
 
         const auto fpsOpt = snapshot.displayModes().get(modeId).transform(
-                [](const DisplayModePtr& mode) { return mode->getFps(); });
+                [](const DisplayModePtr& mode) { return mode->getPeakFps(); });
 
         if (!fpsOpt) {
             ALOGE("%s: Invalid mode %d for display %s", whence, modeId.value(),
@@ -1302,7 +1303,7 @@
     }
 
     const auto& activeMode = *upcomingModeInfo.modeOpt;
-    display.finalizeModeChange(activeMode.modePtr->getId(), activeMode.modePtr->getFps(),
+    display.finalizeModeChange(activeMode.modePtr->getId(), activeMode.modePtr->getVsyncRate(),
                                activeMode.fps);
 
     if (displayId == mActiveDisplayId) {
@@ -1327,10 +1328,10 @@
     const auto desiredActiveMode = display->getDesiredActiveMode();
     const auto& modeOpt = desiredActiveMode->modeOpt;
     const auto displayId = modeOpt->modePtr->getPhysicalDisplayId();
-    const auto displayFps = modeOpt->modePtr->getFps();
+    const auto vsyncRate = modeOpt->modePtr->getVsyncRate();
     const auto renderFps = modeOpt->fps;
     clearDesiredActiveModeState(display);
-    mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, displayFps);
+    mScheduler->resyncToHardwareVsync(displayId, true /* allowToEnable */, vsyncRate);
     mScheduler->setRenderRate(displayId, renderFps);
 
     if (displayId == mActiveDisplayId) {
@@ -1354,8 +1355,7 @@
             continue;
         }
 
-        if (!display->isPoweredOn()) {
-            // Display is no longer powered on, so abort the mode change.
+        if (!shouldApplyRefreshRateSelectorPolicy(*display)) {
             clearDesiredActiveModeState(display);
             continue;
         }
@@ -1371,7 +1371,7 @@
         }
 
         ALOGV("%s changing active mode to %d(%s) for display %s", __func__, desiredModeId.value(),
-              to_string(displayModePtrOpt->get()->getFps()).c_str(),
+              to_string(displayModePtrOpt->get()->getVsyncRate()).c_str(),
               to_string(display->getId()).c_str());
 
         if (display->getActiveMode() == desiredActiveMode->modeOpt) {
@@ -2083,6 +2083,17 @@
 
 void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t timestamp,
                                         std::optional<hal::VsyncPeriodNanos> vsyncPeriod) {
+    if (mConnectedDisplayFlagValue) {
+        // use ~0 instead of -1 as AidlComposerHal.cpp passes the param as unsigned int32
+        if (mIsHotplugErrViaNegVsync && timestamp < 0 && vsyncPeriod.has_value() &&
+            vsyncPeriod.value() == ~0) {
+            int hotplugErrorCode = static_cast<int32_t>(-timestamp);
+            ALOGD("SurfaceFlinger got hotplugErrorCode=%d", hotplugErrorCode);
+            mScheduler->onHotplugConnectionError(mAppConnectionHandle, hotplugErrorCode);
+            return;
+        }
+    }
+
     ATRACE_NAME(vsyncPeriod
                         ? ftl::Concat(__func__, ' ', hwcDisplayId, ' ', *vsyncPeriod, "ns").c_str()
                         : ftl::Concat(__func__, ' ', hwcDisplayId).c_str());
@@ -3211,6 +3222,7 @@
                                      .setPhysicalDisplayId(displayId)
                                      .setResolution({hwcMode.width, hwcMode.height})
                                      .setVsyncPeriod(hwcMode.vsyncPeriod)
+                                     .setVrrConfig(hwcMode.vrrConfig)
                                      .setDpiX(hwcMode.dpiX)
                                      .setDpiY(hwcMode.dpiY)
                                      .setGroup(hwcMode.configGroup)
@@ -3434,7 +3446,7 @@
 
     if (const auto& physical = state.physical) {
         const auto& mode = *physical->activeMode;
-        display->setActiveMode(mode.getId(), mode.getFps(), mode.getFps());
+        display->setActiveMode(mode.getId(), mode.getVsyncRate(), mode.getVsyncRate());
     }
 
     display->setLayerFilter(makeLayerFilterForDisplay(display->getId(), state.layerStack));
@@ -3951,8 +3963,9 @@
 
         if (!display) continue;
 
-        if (!display->isPoweredOn()) {
-            ALOGV("%s(%s): Display is powered off", __func__, to_string(displayId).c_str());
+        if (ftl::FakeGuard guard(kMainThreadContext);
+            !shouldApplyRefreshRateSelectorPolicy(*display)) {
+            ALOGV("%s(%s): Skipped applying policy", __func__, to_string(displayId).c_str());
             continue;
         }
 
@@ -4048,6 +4061,9 @@
             sp<RegionSamplingThread>::make(*this,
                                            RegionSamplingThread::EnvironmentTimingTunables());
     mFpsReporter = sp<FpsReporter>::make(*mFrameTimeline, *this);
+
+    mIsHotplugErrViaNegVsync =
+            base::GetBoolProperty("debug.sf.hwc_hotplug_error_via_neg_vsync"s, false);
 }
 
 void SurfaceFlinger::updatePhaseConfiguration(Fps refreshRate) {
@@ -5142,7 +5158,8 @@
         if (layer->setApi(s.api)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eSidebandStreamChanged) {
-        if (layer->setSidebandStream(s.sidebandStream)) flags |= eTraversalNeeded;
+        if (layer->setSidebandStream(s.sidebandStream, frameTimelineInfo, postTime))
+            flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eInputInfoChanged) {
         layer->setInputInfo(*s.windowInfoHandle->getInfo());
@@ -5203,6 +5220,14 @@
             flags |= eTraversalNeeded;
         }
     }
+    if (what & layer_state_t::eFrameRateSelectionStrategyChanged) {
+        const scheduler::LayerInfo::FrameRateSelectionStrategy strategy =
+                scheduler::LayerInfo::convertFrameRateSelectionStrategy(
+                        s.frameRateSelectionStrategy);
+        if (layer->setFrameRateSelectionStrategy(strategy)) {
+            flags |= eTraversalNeeded;
+        }
+    }
     if (what & layer_state_t::eFixedTransformHintChanged) {
         if (layer->setFixedTransformHint(s.fixedTransformHint)) {
             flags |= eTraversalNeeded | eTransformHintUpdateNeeded;
@@ -5382,11 +5407,19 @@
     // TODO(b/238781169) remove after screenshot refactor, currently screenshots
     // requires to read drawing state from binder thread. So we need to fix that
     // before removing this.
+    if (what & layer_state_t::eBufferTransformChanged) {
+        if (layer->setTransform(s.bufferTransform)) flags |= eTraversalNeeded;
+    }
+    if (what & layer_state_t::eTransformToDisplayInverseChanged) {
+        if (layer->setTransformToDisplayInverse(s.transformToDisplayInverse))
+            flags |= eTraversalNeeded;
+    }
     if (what & layer_state_t::eCropChanged) {
         if (layer->setCrop(s.crop)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eSidebandStreamChanged) {
-        if (layer->setSidebandStream(s.sidebandStream)) flags |= eTraversalNeeded;
+        if (layer->setSidebandStream(s.sidebandStream, frameTimelineInfo, postTime))
+            flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eDataspaceChanged) {
         if (layer->setDataspace(s.dataspace)) flags |= eTraversalNeeded;
@@ -5677,7 +5710,7 @@
 
     display->setPowerMode(mode);
 
-    const auto refreshRate = display->refreshRateSelector().getActiveMode().modePtr->getFps();
+    const auto refreshRate = display->refreshRateSelector().getActiveMode().modePtr->getVsyncRate();
     if (!currentModeOpt || *currentModeOpt == hal::PowerMode::OFF) {
         // Turn on the display
 
@@ -5809,6 +5842,20 @@
         StringAppendF(&result, "Permission Denial: can't dump SurfaceFlinger from pid=%d, uid=%d\n",
                       pid, uid);
     } else {
+        Dumper hwclayersDump = [this](const DumpArgs&, bool, std::string& result)
+                                       FTL_FAKE_GUARD(mStateLock) -> void const {
+            if (mLayerLifecycleManagerEnabled) {
+                mScheduler
+                        ->schedule([this, &result]() FTL_FAKE_GUARD(kMainThreadContext)
+                                           FTL_FAKE_GUARD(mStateLock) {
+                                               dumpHwcLayersMinidump(result);
+                                           })
+                        .get();
+            } else {
+                dumpHwcLayersMinidumpLockedLegacy(result);
+            }
+        };
+
         static const std::unordered_map<std::string, Dumper> dumpers = {
                 {"--comp-displays"s, dumper(&SurfaceFlinger::dumpCompositionDisplays)},
                 {"--display-id"s, dumper(&SurfaceFlinger::dumpDisplayIdentificationData)},
@@ -5817,7 +5864,7 @@
                 {"--events"s, dumper(&SurfaceFlinger::dumpEvents)},
                 {"--frametimeline"s, argsDumper(&SurfaceFlinger::dumpFrameTimeline)},
                 {"--hdrinfo"s, dumper(&SurfaceFlinger::dumpHdrInfo)},
-                {"--hwclayers"s, dumper(&SurfaceFlinger::dumpHwcLayersMinidumpLockedLegacy)},
+                {"--hwclayers"s, std::move(hwclayersDump)},
                 {"--latency"s, argsDumper(&SurfaceFlinger::dumpStatsLocked)},
                 {"--latency-clear"s, argsDumper(&SurfaceFlinger::clearStatsLocked)},
                 {"--list"s, dumper(&SurfaceFlinger::listLayersLocked)},
@@ -6385,6 +6432,8 @@
     StringAppendF(&result, "Misc2FlagValue: %s (%s after boot)\n",
                   mMisc2FlagLateBootValue ? "true" : "false",
                   mMisc2FlagEarlyBootValue == mMisc2FlagLateBootValue ? "stable" : "modified");
+    StringAppendF(&result, "VrrConfigFlagValue: %s\n",
+                  flagutils::vrrConfigEnabled() ? "true" : "false");
 
     getRenderEngine().dump(result);
 
@@ -6404,7 +6453,7 @@
         std::string fps, xDpi, yDpi;
         if (const auto activeModePtr =
                     display->refreshRateSelector().getActiveMode().modePtr.get()) {
-            fps = to_string(activeModePtr->getFps());
+            fps = to_string(activeModePtr->getVsyncRate());
 
             const auto dpi = activeModePtr->getDpi();
             xDpi = base::StringPrintf("%.2f", dpi.x);
@@ -6993,7 +7042,10 @@
             }
             case 1041: { // Transaction tracing
                 if (mTransactionTracing) {
-                    if (data.readInt32()) {
+                    int arg = data.readInt32();
+                    if (arg == -1) {
+                        mTransactionTracing.reset();
+                    } else if (arg > 0) {
                         // Transaction tracing is always running but allow the user to temporarily
                         // increase the buffer when actively debugging.
                         mTransactionTracing->setBufferSize(
@@ -7731,6 +7783,7 @@
     ATRACE_CALL();
 
     auto layers = getLayerSnapshots();
+
     for (auto& [_, layerFE] : layers) {
         frontend::LayerSnapshot* snapshot = layerFE->mSnapshot.get();
         captureResults.capturedSecureLayers |= (snapshot->isVisible && snapshot->isSecure);
@@ -7782,19 +7835,30 @@
                     pickBestDataspace(requestedDataspace, display, captureResults.capturedHdrLayers,
                                       renderArea->getHintForSeamlessTransition());
             sdrWhitePointNits = state.sdrWhitePointNits;
-            displayBrightnessNits = state.displayBrightnessNits;
-            // Only clamp the display brightness if this is not a seamless transition. Otherwise
-            // for seamless transitions it's important to match the current display state as the
-            // buffer will be shown under these same conditions, and we want to avoid any flickers
-            if (sdrWhitePointNits > 1.0f && !renderArea->getHintForSeamlessTransition()) {
-                // Restrict the amount of HDR "headroom" in the screenshot to avoid over-dimming
-                // the SDR portion. 2.0 chosen by experimentation
-                constexpr float kMaxScreenshotHeadroom = 2.0f;
-                displayBrightnessNits =
-                        std::min(sdrWhitePointNits * kMaxScreenshotHeadroom, displayBrightnessNits);
+
+             // TODO(b/298219334): Clean this up once we verify this doesn't break anything
+             static constexpr bool kScreenshotsDontDim = true;
+
+            if (kScreenshotsDontDim && !captureResults.capturedHdrLayers) {
+                displayBrightnessNits = sdrWhitePointNits;
+            } else {
+                displayBrightnessNits = state.displayBrightnessNits;
+                // Only clamp the display brightness if this is not a seamless transition. Otherwise
+                // for seamless transitions it's important to match the current display state as the
+                // buffer will be shown under these same conditions, and we want to avoid any
+                // flickers
+                if (sdrWhitePointNits > 1.0f && !renderArea->getHintForSeamlessTransition()) {
+                    // Restrict the amount of HDR "headroom" in the screenshot to avoid over-dimming
+                    // the SDR portion. 2.0 chosen by experimentation
+                    constexpr float kMaxScreenshotHeadroom = 2.0f;
+                    displayBrightnessNits = std::min(sdrWhitePointNits * kMaxScreenshotHeadroom,
+                                                     displayBrightnessNits);
+                }
             }
 
-            if (requestedDataspace == ui::Dataspace::UNKNOWN) {
+            // Screenshots leaving the device should be colorimetric
+            if (requestedDataspace == ui::Dataspace::UNKNOWN &&
+                renderArea->getHintForSeamlessTransition()) {
                 renderIntent = state.renderIntent;
             }
         }
@@ -7838,6 +7902,10 @@
             }
         }
 
+        // Screenshots leaving the device must not dim in gamma space.
+        const bool dimInGammaSpaceForEnhancedScreenshots = mDimInGammaSpaceForEnhancedScreenshots &&
+                renderArea->getHintForSeamlessTransition();
+
         std::shared_ptr<ScreenCaptureOutput> output = createScreenCaptureOutput(
                 ScreenCaptureOutputArgs{.compositionEngine = *compositionEngine,
                                         .colorProfile = colorProfile,
@@ -7850,7 +7918,7 @@
                                         .regionSampling = regionSampling,
                                         .treat170mAsSrgb = mTreat170mAsSrgb,
                                         .dimInGammaSpaceForEnhancedScreenshots =
-                                                mDimInGammaSpaceForEnhancedScreenshots});
+                                                dimInGammaSpaceForEnhancedScreenshots});
 
         const float colorSaturation = grayscale ? 0 : 1;
         compositionengine::CompositionRefreshArgs refreshArgs{
@@ -7967,7 +8035,7 @@
                 return snapshot.displayModes().get(defaultModeId);
             })
             .transform([](const DisplayModePtr& modePtr) {
-                return scheduler::FrameRateMode{modePtr->getFps(), ftl::as_non_null(modePtr)};
+                return scheduler::FrameRateMode{modePtr->getPeakFps(), ftl::as_non_null(modePtr)};
             });
 }
 
@@ -7996,17 +8064,33 @@
             break;
     }
 
-    // TODO(b/255635711): Apply the policy once the display is powered on, which is currently only
-    // done for the internal display that becomes active on fold/unfold. For now, assume that DM
-    // always powers on the secondary (internal or external) display before setting its policy.
-    if (!display->isPoweredOn()) {
-        ALOGV("%s(%s): Display is powered off", __func__, to_string(displayId).c_str());
+    if (!shouldApplyRefreshRateSelectorPolicy(*display)) {
+        ALOGV("%s(%s): Skipped applying policy", __func__, to_string(displayId).c_str());
         return NO_ERROR;
     }
 
     return applyRefreshRateSelectorPolicy(displayId, selector);
 }
 
+bool SurfaceFlinger::shouldApplyRefreshRateSelectorPolicy(const DisplayDevice& display) const {
+    if (display.isPoweredOn() || mPhysicalDisplays.size() == 1) return true;
+
+    LOG_ALWAYS_FATAL_IF(display.isVirtual());
+    const auto displayId = display.getPhysicalId();
+
+    // The display is powered off, and this is a multi-display device. If the display is the
+    // inactive internal display of a dual-display foldable, then the policy will be applied
+    // when it becomes active upon powering on.
+    //
+    // TODO(b/255635711): Remove this function (i.e. returning `false` as a special case) once
+    // concurrent mode setting across multiple (potentially powered off) displays is supported.
+    //
+    return displayId == mActiveDisplayId ||
+            !mPhysicalDisplays.get(displayId)
+                     .transform(&PhysicalDisplay::isInternal)
+                     .value_or(false);
+}
+
 status_t SurfaceFlinger::applyRefreshRateSelectorPolicy(
         PhysicalDisplayId displayId, const scheduler::RefreshRateSelector& selector, bool force) {
     const scheduler::RefreshRateSelector::Policy currentPolicy = selector.getCurrentPolicy();
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index e22dd56..42825f7 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -705,6 +705,9 @@
             const sp<DisplayDevice>&, const scheduler::RefreshRateSelector::PolicyVariant&)
             EXCLUDES(mStateLock) REQUIRES(kMainThreadContext);
 
+    bool shouldApplyRefreshRateSelectorPolicy(const DisplayDevice&) const
+            REQUIRES(mStateLock, kMainThreadContext);
+
     // TODO(b/241285191): Look up RefreshRateSelector on Scheduler to remove redundant parameter.
     status_t applyRefreshRateSelectorPolicy(PhysicalDisplayId,
                                             const scheduler::RefreshRateSelector&,
@@ -1233,6 +1236,8 @@
         hal::Connection connection = hal::Connection::INVALID;
     };
 
+    bool mIsHotplugErrViaNegVsync = false;
+
     std::mutex mHotplugMutex;
     std::vector<HotplugEvent> mPendingHotplugEvents GUARDED_BY(mHotplugMutex);
 
diff --git a/services/surfaceflinger/Tracing/LayerDataSource.cpp b/services/surfaceflinger/Tracing/LayerDataSource.cpp
index 474fef8..25e768e 100644
--- a/services/surfaceflinger/Tracing/LayerDataSource.cpp
+++ b/services/surfaceflinger/Tracing/LayerDataSource.cpp
@@ -52,7 +52,7 @@
         mMode = static_cast<LayerTracing::Mode>(config.mode());
     } else {
         mMode = LayerTracing::Mode::MODE_GENERATED;
-        ALOGV("Received config with unspecified 'mode'. Using 'GENERATED' as default");
+        ALOGD("Received config with unspecified 'mode'. Using 'GENERATED' as default");
     }
 
     mFlags = 0;
@@ -62,21 +62,21 @@
 }
 
 void LayerDataSource::OnStart(const LayerDataSource::StartArgs&) {
-    ALOGV("Received OnStart event (mode = 0x%02x, flags = 0x%02x)", mMode, mFlags);
+    ALOGD("Received OnStart event (mode = 0x%02x, flags = 0x%02x)", mMode, mFlags);
     if (auto* p = mLayerTracing.load()) {
         p->onStart(mMode, mFlags);
     }
 }
 
 void LayerDataSource::OnFlush(const LayerDataSource::FlushArgs&) {
-    ALOGV("Received OnFlush event (mode = 0x%02x, flags = 0x%02x)", mMode, mFlags);
+    ALOGD("Received OnFlush event (mode = 0x%02x, flags = 0x%02x)", mMode, mFlags);
     if (auto* p = mLayerTracing.load()) {
         p->onFlush(mMode, mFlags);
     }
 }
 
 void LayerDataSource::OnStop(const LayerDataSource::StopArgs&) {
-    ALOGV("Received OnStop event (mode = 0x%02x, flags = 0x%02x)", mMode, mFlags);
+    ALOGD("Received OnStop event (mode = 0x%02x, flags = 0x%02x)", mMode, mFlags);
     if (auto* p = mLayerTracing.load()) {
         p->onStop(mMode);
     }
diff --git a/services/surfaceflinger/Tracing/LayerTracing.cpp b/services/surfaceflinger/Tracing/LayerTracing.cpp
index e55b4c2..403e105 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.cpp
+++ b/services/surfaceflinger/Tracing/LayerTracing.cpp
@@ -36,6 +36,10 @@
     LayerDataSource::Initialize(*this);
 }
 
+LayerTracing::LayerTracing(std::ostream& outStream) : LayerTracing() {
+    mOutStream = std::ref(outStream);
+}
+
 LayerTracing::~LayerTracing() {
     LayerDataSource::UnregisterLayerTracing();
 }
@@ -49,10 +53,6 @@
     mTransactionTracing = &transactionTracing;
 }
 
-void LayerTracing::setOutputStream(std::ostream& outStream) {
-    mOutStream = std::ref(outStream);
-}
-
 void LayerTracing::onStart(Mode mode, uint32_t flags) {
     switch (mode) {
         case Mode::MODE_ACTIVE: {
@@ -63,18 +63,17 @@
             // taken. Let's manually take a snapshot, so that the trace's first entry will contain
             // the current layers state.
             addProtoSnapshotToOstream(mTakeLayersSnapshotProto(flags), Mode::MODE_ACTIVE);
-            ALOGV("Started active tracing (traced initial snapshot)");
+            ALOGD("Started active tracing (traced initial snapshot)");
             break;
         }
         case Mode::MODE_GENERATED: {
-            ALOGV("Started generated tracing (waiting for OnFlush event to generated layers)");
+            ALOGD("Started generated tracing (waiting for OnFlush event to generated layers)");
             break;
         }
         case Mode::MODE_DUMP: {
-            ALOGV("Starting dump tracing (dumping single snapshot)");
             auto snapshot = mTakeLayersSnapshotProto(flags);
             addProtoSnapshotToOstream(std::move(snapshot), Mode::MODE_DUMP);
-            ALOGV("Started dump tracing (dumped single snapshot)");
+            ALOGD("Started dump tracing (dumped single snapshot)");
             break;
         }
         default: {
@@ -91,19 +90,19 @@
     }
 
     if (!mTransactionTracing) {
-        ALOGV("Skipping layers trace generation (transactions tracing disabled)");
+        ALOGD("Skipping layers trace generation (transactions tracing disabled)");
         return;
     }
 
     auto transactionTrace = mTransactionTracing->writeToProto();
-    LayerTraceGenerator{}.generate(transactionTrace, flags);
-    ALOGV("Flushed generated tracing");
+    LayerTraceGenerator{}.generate(transactionTrace, flags, *this);
+    ALOGD("Flushed generated tracing");
 }
 
 void LayerTracing::onStop(Mode mode) {
     if (mode == Mode::MODE_ACTIVE) {
         mIsActiveTracingStarted.store(false);
-        ALOGV("Stopped active tracing");
+        ALOGD("Stopped active tracing");
     }
 }
 
diff --git a/services/surfaceflinger/Tracing/LayerTracing.h b/services/surfaceflinger/Tracing/LayerTracing.h
index 349cc40..fe7f06d 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.h
+++ b/services/surfaceflinger/Tracing/LayerTracing.h
@@ -97,11 +97,11 @@
     };
 
     LayerTracing();
+    LayerTracing(std::ostream&);
     ~LayerTracing();
     void setTakeLayersSnapshotProtoFunction(
             const std::function<perfetto::protos::LayersSnapshotProto(uint32_t)>&);
     void setTransactionTracing(TransactionTracing&);
-    void setOutputStream(std::ostream&);
 
     // Start event from perfetto data source
     void onStart(Mode mode, uint32_t flags);
diff --git a/services/surfaceflinger/Tracing/TransactionDataSource.cpp b/services/surfaceflinger/Tracing/TransactionDataSource.cpp
index 05b89b8..6c9ed30 100644
--- a/services/surfaceflinger/Tracing/TransactionDataSource.cpp
+++ b/services/surfaceflinger/Tracing/TransactionDataSource.cpp
@@ -49,26 +49,26 @@
         mMode = static_cast<TransactionTracing::Mode>(config.mode());
     } else {
         mMode = TransactionTracing::Mode::MODE_CONTINUOUS;
-        ALOGV("Received config with unspecified 'mode'. Using 'CONTINUOUS' as default");
+        ALOGD("Received config with unspecified 'mode'. Using 'CONTINUOUS' as default");
     }
 }
 
 void TransactionDataSource::OnStart(const StartArgs&) {
-    ALOGV("Received OnStart event");
+    ALOGD("Received OnStart event");
     if (auto* p = mTransactionTracing.load()) {
         p->onStart(mMode);
     }
 }
 
 void TransactionDataSource::OnFlush(const FlushArgs&) {
-    ALOGV("Received OnFlush event");
+    ALOGD("Received OnFlush event");
     if (auto* p = mTransactionTracing.load()) {
         p->onFlush(mMode);
     }
 }
 
 void TransactionDataSource::OnStop(const StopArgs&) {
-    ALOGV("Received OnStop event");
+    ALOGD("Received OnStop event");
 }
 
 TransactionTracing::Mode TransactionDataSource::GetMode() const {
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp
index 0517984..9d6d87e 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.cpp
+++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp
@@ -70,7 +70,7 @@
 
     writeRingBufferToPerfetto(TransactionTracing::Mode::MODE_ACTIVE);
 
-    ALOGV("Started active mode tracing (wrote initial transactions ring buffer to perfetto)");
+    ALOGD("Started active mode tracing (wrote initial transactions ring buffer to perfetto)");
 }
 
 void TransactionTracing::onFlush(TransactionTracing::Mode mode) {
@@ -83,7 +83,7 @@
 
     writeRingBufferToPerfetto(TransactionTracing::Mode::MODE_CONTINUOUS);
 
-    ALOGV("Flushed continuous mode tracing (wrote transactions ring buffer to perfetto");
+    ALOGD("Flushed continuous mode tracing (wrote transactions ring buffer to perfetto");
 }
 
 void TransactionTracing::writeRingBufferToPerfetto(TransactionTracing::Mode mode) {
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
index 62c362e..23fe8fa 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
@@ -41,8 +41,7 @@
 using namespace ftl::flag_operators;
 
 bool LayerTraceGenerator::generate(const perfetto::protos::TransactionTraceFile& traceFile,
-                                   std::uint32_t traceFlags,
-                                   std::optional<std::reference_wrapper<std::ostream>> outStream,
+                                   std::uint32_t traceFlags, LayerTracing& layerTracing,
                                    bool onlyLastEntry) {
     if (traceFile.entry_size() == 0) {
         ALOGD("Trace file is empty");
@@ -51,11 +50,6 @@
 
     TransactionProtoParser parser(std::make_unique<TransactionProtoParser::FlingerDataMapper>());
 
-    LayerTracing layerTracing;
-    if (outStream) {
-        layerTracing.setOutputStream(outStream->get());
-    }
-
     // frontend
     frontend::LayerLifecycleManager lifecycleManager;
     frontend::LayerHierarchyBuilder hierarchyBuilder{{}};
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
index 2bb6f51..e4d02ca 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
@@ -30,7 +30,6 @@
 class LayerTraceGenerator {
 public:
     bool generate(const perfetto::protos::TransactionTraceFile&, std::uint32_t traceFlags,
-                  std::optional<std::reference_wrapper<std::ostream>> outStream = std::nullopt,
-                  bool onlyLastEntry = false);
+                  LayerTracing& layerTracing, bool onlyLastEntry = false);
 };
 } // namespace android
diff --git a/services/surfaceflinger/Tracing/tools/main.cpp b/services/surfaceflinger/Tracing/tools/main.cpp
index a8ac36a..698ef06 100644
--- a/services/surfaceflinger/Tracing/tools/main.cpp
+++ b/services/surfaceflinger/Tracing/tools/main.cpp
@@ -50,7 +50,9 @@
 
     const auto* outputLayersTracePath =
             (argc == 3) ? argv[2] : "/data/misc/wmtrace/layers_trace.winscope";
-    auto outStream = std::ofstream{outputLayersTracePath, std::ios::binary | std::ios::app};
+    auto outStream = std::ofstream{outputLayersTracePath, std::ios::binary | std::ios::out};
+
+    auto layerTracing = LayerTracing{outStream};
 
     const bool generateLastEntryOnly =
             argc >= 4 && std::string_view(argv[3]) == "--last-entry-only";
@@ -60,7 +62,7 @@
     ALOGD("Generating %s...", outputLayersTracePath);
     std::cout << "Generating " << outputLayersTracePath << "\n";
 
-    if (!LayerTraceGenerator().generate(transactionTraceFile, traceFlags, outStream,
+    if (!LayerTraceGenerator().generate(transactionTraceFile, traceFlags, layerTracing,
                                         generateLastEntryOnly)) {
         std::cout << "Error: Failed to generate layers trace " << outputLayersTracePath;
         return -1;
diff --git a/services/surfaceflinger/Utils/FlagUtils.h b/services/surfaceflinger/Utils/FlagUtils.h
new file mode 100644
index 0000000..8435f04
--- /dev/null
+++ b/services/surfaceflinger/Utils/FlagUtils.h
@@ -0,0 +1,33 @@
+/**
+ * Copyright 2023 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 <android-base/properties.h>
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include <string>
+
+namespace android::flagutils {
+
+using namespace std::literals::string_literals;
+using namespace com::android::graphics::surfaceflinger;
+
+inline bool vrrConfigEnabled() {
+    static const bool enable_vrr_config =
+            base::GetBoolProperty("debug.sf.enable_vrr_config"s, false);
+    return flags::vrr_config() || enable_vrr_config;
+}
+} // namespace android::flagutils
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index 437fd35..c4077df 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -666,7 +666,7 @@
         }
 
         mRefreshRateSelector = std::make_shared<scheduler::RefreshRateSelector>(modes, kModeId60);
-        const auto fps = mRefreshRateSelector->getActiveMode().modePtr->getFps();
+        const auto fps = mRefreshRateSelector->getActiveMode().modePtr->getVsyncRate();
         mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps);
 
         mFlinger->mRefreshRateStats =
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
index 9f0bdde..39a7ee5 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
@@ -153,7 +153,8 @@
     native_handle_t* testHandle = native_handle_create(0, 1);
     const bool ownsHandle = mFdp.ConsumeBool();
     sp<NativeHandle> nativeHandle = sp<NativeHandle>::make(testHandle, ownsHandle);
-    layer->setSidebandStream(nativeHandle);
+    layer->setSidebandStream(nativeHandle, getFuzzedFrameTimelineInfo(),
+                             mFdp.ConsumeIntegral<nsecs_t>() /* postTime */);
     layer->computeSourceBounds(getFuzzedFloatRect(&mFdp));
 
     layer->fenceHasSignaled();
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index 4d1a5ff..cbbfa16 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -390,7 +390,7 @@
                                       PowerMode::OFF);
 
     const auto fpsOpt = displayModes.get(modeId).transform(
-            [](const DisplayModePtr& mode) { return mode->getFps(); });
+            [](const DisplayModePtr& mode) { return mode->getVsyncRate(); });
     refreshRateStats.setRefreshRate(*fpsOpt);
 
     refreshRateStats.setPowerMode(mFdp.PickValueInArray(kPowerModes));
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
index 6519d3a..bfc03aa 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -22,3 +22,11 @@
   description: "This flag controls minor miscellaneous SurfaceFlinger changes. Cannot be read before boot finished!"
   bug: "297389311"
 }
+
+flag {
+  name: "vrr_config"
+  namespace: "core_graphics"
+  description: "Controls SurfaceFlinger support for VRR Configurations"
+  bug: "284845445"
+  is_fixed_read_only: true
+}
diff --git a/services/surfaceflinger/tests/tracing/Android.bp b/services/surfaceflinger/tests/tracing/Android.bp
index b6cd7b8..aeceadb 100644
--- a/services/surfaceflinger/tests/tracing/Android.bp
+++ b/services/surfaceflinger/tests/tracing/Android.bp
@@ -29,9 +29,6 @@
         "skia_renderengine_deps",
     ],
     test_suites: ["device-tests"],
-    sanitize: {
-        address: true,
-    },
     srcs: [
         ":libsurfaceflinger_sources",
         ":libsurfaceflinger_mock_sources",
diff --git a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
index 7a07634..2fcb9e0 100644
--- a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
+++ b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
@@ -23,6 +23,7 @@
 #include <unordered_map>
 
 #include <LayerProtoHelper.h>
+#include <Tracing/LayerTracing.h>
 #include <Tracing/TransactionProtoParser.h>
 #include <Tracing/tools/LayerTraceGenerator.h>
 #include <layerproto/LayerProtoHeader.h>
@@ -62,7 +63,8 @@
         {
             auto traceFlags = LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS;
             std::ofstream outStream{actualLayersTracePath, std::ios::binary | std::ios::app};
-            EXPECT_TRUE(LayerTraceGenerator().generate(mTransactionTrace, traceFlags, outStream,
+            auto layerTracing = LayerTracing{outStream};
+            EXPECT_TRUE(LayerTraceGenerator().generate(mTransactionTrace, traceFlags, layerTracing,
                                                        /*onlyLastEntry=*/true))
                     << "Failed to generate layers trace from " << transactionTracePath;
         }
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 8deff85..f4516c7 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -87,6 +87,7 @@
         "FramebufferSurfaceTest.cpp",
         "FrameRateOverrideMappingsTest.cpp",
         "FrameRateSelectionPriorityTest.cpp",
+        "FrameRateSelectionStrategyTest.cpp",
         "FrameTimelineTest.cpp",
         "GameModeTest.cpp",
         "HWComposerTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 91c6239..9b46009 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -703,7 +703,7 @@
                               .setId(DisplayModeId(7))
                               .setVsyncPeriod(16666666)
                               .build();
-    const Fps fps = mode->getFps() / 2;
+    const Fps fps = mode->getPeakFps() / 2;
 
     mThread->onModeChanged({fps, ftl::as_non_null(mode)});
     expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 7, fps.getPeriodNsecs());
@@ -717,7 +717,7 @@
                               .setId(DisplayModeId(5))
                               .setVsyncPeriod(16666666)
                               .build();
-    const Fps fps = mode->getFps() / 2;
+    const Fps fps = mode->getPeakFps() / 2;
 
     mThread->onModeChanged({fps, ftl::as_non_null(mode)});
     expectConfigChangedEventReceivedByConnection(EXTERNAL_DISPLAY_ID, 5, fps.getPeriodNsecs());
@@ -731,7 +731,7 @@
                               .setId(DisplayModeId(7))
                               .setVsyncPeriod(16666666)
                               .build();
-    const Fps fps = mode->getFps() / 2;
+    const Fps fps = mode->getPeakFps() / 2;
     mThread->onModeChanged({fps, ftl::as_non_null(mode)});
     expectConfigChangedEventReceivedByConnection(DISPLAY_ID_64BIT, 7, fps.getPeriodNsecs());
 }
@@ -748,7 +748,7 @@
                               .setId(DisplayModeId(9))
                               .setVsyncPeriod(16666666)
                               .build();
-    const Fps fps = mode->getFps() / 2;
+    const Fps fps = mode->getPeakFps() / 2;
 
     mThread->onModeChanged({fps, ftl::as_non_null(mode)});
     expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 9, fps.getPeriodNsecs());
diff --git a/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp
new file mode 100644
index 0000000..20ea0c0
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2023 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <gui/LayerMetadata.h>
+
+#include "Layer.h"
+#include "LayerTestUtils.h"
+#include "TestableSurfaceFlinger.h"
+#include "mock/DisplayHardware/MockComposer.h"
+
+namespace android {
+
+using testing::DoAll;
+using testing::Mock;
+using testing::SetArgPointee;
+
+using android::Hwc2::IComposer;
+using android::Hwc2::IComposerClient;
+
+using scheduler::LayerHistory;
+
+using FrameRate = Layer::FrameRate;
+using FrameRateCompatibility = Layer::FrameRateCompatibility;
+using FrameRateSelectionStrategy = scheduler::LayerInfo::FrameRateSelectionStrategy;
+
+/**
+ * This class tests the behaviour of Layer::setFrameRateSelectionStrategy.
+ */
+class FrameRateSelectionStrategyTest : public BaseLayerTest {
+protected:
+    const FrameRate FRAME_RATE_VOTE1 = FrameRate(11_Hz, FrameRateCompatibility::Default);
+    const FrameRate FRAME_RATE_VOTE2 = FrameRate(22_Hz, FrameRateCompatibility::Default);
+    const FrameRate FRAME_RATE_VOTE3 = FrameRate(33_Hz, FrameRateCompatibility::Default);
+    const FrameRate FRAME_RATE_TREE = FrameRate(Fps(), FrameRateCompatibility::NoVote);
+
+    FrameRateSelectionStrategyTest();
+
+    void addChild(sp<Layer> layer, sp<Layer> child);
+    void removeChild(sp<Layer> layer, sp<Layer> child);
+    void commitTransaction();
+
+    std::vector<sp<Layer>> mLayers;
+};
+
+FrameRateSelectionStrategyTest::FrameRateSelectionStrategyTest() {
+    const ::testing::TestInfo* const test_info =
+            ::testing::UnitTest::GetInstance()->current_test_info();
+    ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+
+    mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
+}
+
+void FrameRateSelectionStrategyTest::addChild(sp<Layer> layer, sp<Layer> child) {
+    layer->addChild(child);
+}
+
+void FrameRateSelectionStrategyTest::removeChild(sp<Layer> layer, sp<Layer> child) {
+    layer->removeChild(child);
+}
+
+void FrameRateSelectionStrategyTest::commitTransaction() {
+    for (auto layer : mLayers) {
+        layer->commitTransaction();
+    }
+}
+
+namespace {
+
+INSTANTIATE_TEST_SUITE_P(PerLayerType, FrameRateSelectionStrategyTest,
+                         testing::Values(std::make_shared<BufferStateLayerFactory>(),
+                                         std::make_shared<EffectLayerFactory>()),
+                         PrintToStringParamName);
+
+TEST_P(FrameRateSelectionStrategyTest, SetAndGet) {
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+
+    const auto& layerFactory = GetParam();
+    auto layer = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+    layer->setFrameRate(FRAME_RATE_VOTE1.vote);
+    layer->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
+    commitTransaction();
+    EXPECT_EQ(FRAME_RATE_VOTE1, layer->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+              layer->getDrawingState().frameRateSelectionStrategy);
+}
+
+TEST_P(FrameRateSelectionStrategyTest, SetChildAndGetParent) {
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+
+    const auto& layerFactory = GetParam();
+    auto parent = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+    auto child1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+    auto child2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+    addChild(parent, child1);
+    addChild(child1, child2);
+
+    child2->setFrameRate(FRAME_RATE_VOTE1.vote);
+    child2->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
+    commitTransaction();
+    EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::Self,
+              parent->getDrawingState().frameRateSelectionStrategy);
+    EXPECT_EQ(FRAME_RATE_TREE, child1->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::Self,
+              child1->getDrawingState().frameRateSelectionStrategy);
+    EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+              child2->getDrawingState().frameRateSelectionStrategy);
+}
+
+TEST_P(FrameRateSelectionStrategyTest, SetParentAndGet) {
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+
+    const auto& layerFactory = GetParam();
+    auto layer1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+    auto layer2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+    auto layer3 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+    addChild(layer1, layer2);
+    addChild(layer2, layer3);
+
+    layer1->setFrameRate(FRAME_RATE_VOTE1.vote);
+    layer1->setFrameRate(FRAME_RATE_VOTE1.vote);
+    layer1->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
+    layer2->setFrameRate(FRAME_RATE_VOTE2.vote);
+    layer2->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
+    layer3->setFrameRate(FRAME_RATE_VOTE3.vote);
+    commitTransaction();
+
+    EXPECT_EQ(FRAME_RATE_VOTE1, layer1->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+              layer1->getDrawingState().frameRateSelectionStrategy);
+    EXPECT_EQ(FRAME_RATE_VOTE1, layer2->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+              layer2->getDrawingState().frameRateSelectionStrategy);
+    EXPECT_EQ(FRAME_RATE_VOTE1, layer3->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::Self,
+              layer3->getDrawingState().frameRateSelectionStrategy);
+
+    layer1->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::Self);
+    commitTransaction();
+
+    EXPECT_EQ(FRAME_RATE_VOTE1, layer1->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::Self,
+              layer1->getDrawingState().frameRateSelectionStrategy);
+    EXPECT_EQ(FRAME_RATE_VOTE2, layer2->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+              layer2->getDrawingState().frameRateSelectionStrategy);
+    EXPECT_EQ(FRAME_RATE_VOTE2, layer3->getFrameRateForLayerTree());
+    EXPECT_EQ(FrameRateSelectionStrategy::Self,
+              layer3->getDrawingState().frameRateSelectionStrategy);
+}
+
+} // namespace
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
index ff644ba..d7ac038 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -346,6 +346,18 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
+    void setFrameRateSelectionStrategy(uint32_t id, int8_t strategy) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what =
+                layer_state_t::eFrameRateSelectionStrategyChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.frameRateSelectionStrategy = strategy;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
     void setRoundedCorners(uint32_t id, float radius) {
         std::vector<TransactionState> transactions;
         transactions.emplace_back();
@@ -372,6 +384,17 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
+    void setBufferCrop(uint32_t id, const Rect& bufferCrop) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eBufferCropChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.bufferCrop = bufferCrop;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
     void setDataspace(uint32_t id, ui::Dataspace dataspace) {
         std::vector<TransactionState> transactions;
         transactions.emplace_back();
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index b7996ce..7e3e61f 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -494,14 +494,14 @@
         time += HI_FPS_PERIOD;
     }
 
-    ASSERT_TRUE(summarizeLayerHistory(time).empty());
+    EXPECT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
     // layer became inactive
     time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
-    ASSERT_TRUE(summarizeLayerHistory(time).empty());
-    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
 }
 
@@ -1124,8 +1124,8 @@
 
     LayerHistory::Summary summary;
 
-    // layer1 is active but infrequent.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+    // layer1 is updating small dirty.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
         auto props = layer1->getLayerProps();
         props.isSmallDirty = true;
         history().record(layer1->getSequence(), props, 0 /*presentTime*/, time,
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 1227b99..1a9233d 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -743,6 +743,50 @@
     EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
 }
 
+TEST_F(LayerSnapshotTest, frameRateSelectionStrategy) {
+    // ROOT
+    // ├── 1
+    // │   ├── 11
+    // │   │   └── 111
+    // │   ├── 12 (frame rate set to 244.f with strategy OverrideChildren)
+    // │   │   ├── 121
+    // │   │   └── 122 (frame rate set to 123.f but should be overridden by layer 12)
+    // │   │       └── 1221
+    // │   └── 13
+    // └── 2
+    setFrameRate(12, 244.f, 0, 0);
+    setFrameRate(122, 123.f, 0, 0);
+    setFrameRateSelectionStrategy(12, 1 /* OverrideChildren */);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    // verify parent 1 gets no vote
+    EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+              scheduler::LayerInfo::FrameRateCompatibility::NoVote);
+    EXPECT_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // verify layer 12 and all descendants (121, 122, 1221) get the requested vote
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 12})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 121})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 121})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 121})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.vote.rate.getValue(), 244.f);
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+}
+
 TEST_F(LayerSnapshotTest, skipRoundCornersWhenProtected) {
     setRoundedCorners(1, 42.f);
     setRoundedCorners(2, 42.f);
@@ -802,4 +846,33 @@
               aidl::android::hardware::graphics::composer3::Composition::REFRESH_RATE_INDICATOR);
 }
 
+TEST_F(LayerSnapshotTest, setBufferCrop) {
+    // validate no buffer but has crop
+    Rect crop = Rect(0, 0, 50, 50);
+    setBufferCrop(1, crop);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot(1)->geomContentCrop, crop);
+
+    setBuffer(1,
+              std::make_shared<renderengine::mock::FakeExternalTexture>(100U /*width*/,
+                                                                        100U /*height*/,
+                                                                        42ULL /* bufferId */,
+                                                                        HAL_PIXEL_FORMAT_RGBA_8888,
+                                                                        0 /*usage*/));
+    // validate a buffer crop within the buffer bounds
+    setBufferCrop(1, crop);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot(1)->geomContentCrop, crop);
+
+    // validate a buffer crop outside the buffer bounds
+    crop = Rect(0, 0, 150, 150);
+    setBufferCrop(1, crop);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot(1)->geomContentCrop, Rect(0, 0, 100, 100));
+
+    // validate no buffer crop
+    setBufferCrop(1, Rect());
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot(1)->geomContentCrop, Rect(0, 0, 100, 100));
+}
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index 50c1626..0b67137 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -36,6 +36,9 @@
 
 #include "libsurfaceflinger_unittest_main.h"
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+
+using namespace com::android::graphics::surfaceflinger;
 using namespace std::chrono_literals;
 
 namespace android::scheduler {
@@ -48,6 +51,7 @@
 using SetPolicyResult = RefreshRateSelector::SetPolicyResult;
 
 using mock::createDisplayMode;
+using mock::createVrrDisplayMode;
 
 struct TestableRefreshRateSelector : RefreshRateSelector {
     using RefreshRateSelector::FrameRateRanking;
@@ -201,6 +205,19 @@
     static inline const ftl::NonNull<DisplayModePtr> kMode10 =
             ftl::as_non_null(createDisplayMode(kModeId10, 10_Hz));
 
+    // VRR modes
+    static inline const ftl::NonNull<DisplayModePtr> kVrrMode120TE240 = ftl::as_non_null(
+            createVrrDisplayMode(kModeId120, 240_Hz,
+                                 hal::VrrConfig{
+                                         .minFrameIntervalNs =
+                                                 static_cast<Fps>(120_Hz).getPeriodNsecs()}));
+
+    static inline const ftl::NonNull<DisplayModePtr> kVrrMode60TE120 = ftl::as_non_null(
+            createVrrDisplayMode(kModeId60, 120_Hz,
+                                 hal::VrrConfig{.minFrameIntervalNs =
+                                                        static_cast<Fps>(60_Hz).getPeriodNsecs()},
+                                 /*group=*/1));
+
     // Test configurations.
     static inline const DisplayModes kModes_60 = makeModes(kMode60);
     static inline const DisplayModes kModes_35_60_90 = makeModes(kMode35, kMode60, kMode90);
@@ -225,6 +242,11 @@
     static inline const DisplayModes kModes_1_5_10 = makeModes(kMode1, kMode5, kMode10);
     static inline const DisplayModes kModes_60_90_120 = makeModes(kMode60, kMode90, kMode120);
 
+    // VRR display modes
+    static inline const DisplayModes kVrrMode_120 = makeModes(kVrrMode120TE240);
+    static inline const DisplayModes kVrrModes_60_120 =
+            makeModes(kVrrMode60TE120, kVrrMode120TE240);
+
     // This is a typical TV configuration.
     static inline const DisplayModes kModes_24_25_30_50_60_Frac =
             makeModes(kMode24, kMode24Frac, kMode25, kMode30, kMode30Frac, kMode50, kMode60,
@@ -973,7 +995,8 @@
         lr.desiredRefreshRate = Fps::fromValue(fps);
         const auto mode = selector.getBestFrameRateMode(layers);
         EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses "
-                                 << to_string(mode->getFps());
+                                 << to_string(mode->getPeakFps()) << "("
+                                 << to_string(mode->getVsyncRate()) << ")";
     }
 }
 
@@ -988,7 +1011,8 @@
         lr.desiredRefreshRate = Fps::fromValue(fps);
         const auto mode = selector.getBestFrameRateMode(layers);
         EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses "
-                                 << to_string(mode->getFps());
+                                 << to_string(mode->getPeakFps()) << "("
+                                 << to_string(mode->getVsyncRate()) << ")";
     }
 }
 
@@ -1029,7 +1053,8 @@
         lr.desiredRefreshRate = Fps::fromValue(fps);
         const auto mode = selector.getBestFrameRateMode(layers, {});
         EXPECT_EQ(kMode90, mode) << lr.desiredRefreshRate << " chooses "
-                                 << to_string(mode->getFps());
+                                 << to_string(mode->getPeakFps()) << "("
+                                 << to_string(mode->getVsyncRate()) << ")";
     }
 }
 
@@ -1146,9 +1171,9 @@
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
                 << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
-                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
-                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 }
 
@@ -1175,9 +1200,9 @@
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
                 << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
-                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
-                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 }
 
@@ -1207,9 +1232,9 @@
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
                 << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
-                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
-                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 }
 
@@ -1239,9 +1264,9 @@
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
                 << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
-                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
-                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 }
 
@@ -1267,9 +1292,9 @@
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
                 << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
-                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
-                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 
     std::tie(refreshRates, signals) =
@@ -1281,9 +1306,9 @@
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
                 << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
-                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
-                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
@@ -1301,9 +1326,9 @@
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
                 << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
-                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
-                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 
     std::tie(refreshRates, signals) =
@@ -1326,9 +1351,9 @@
     for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
         EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
                 << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
-                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
-                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 }
 
@@ -1385,11 +1410,6 @@
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_30_60_90_120) {
     auto selector = createSelector(makeModes(kMode30, kMode60, kMode90, kMode120), kModeId60);
 
-    std::vector<LayerRequirement> layers = {{.vote = LayerVoteType::ExplicitDefault, .weight = 1.f},
-                                            {.vote = LayerVoteType::ExplicitCategory,
-                                             .weight = 1.f}};
-    auto& lr = layers[0];
-
     struct Case {
         // Params
         Fps desiredFrameRate = 0_Hz;
@@ -1406,7 +1426,7 @@
             {0_Hz, FrameRateCategory::High, 90_Hz},
             {0_Hz, FrameRateCategory::Normal, 60_Hz},
             {0_Hz, FrameRateCategory::Low, 30_Hz},
-            {0_Hz, FrameRateCategory::NoPreference, 60_Hz},
+            {0_Hz, FrameRateCategory::NoPreference, 30_Hz},
 
             // Cases that have both desired frame rate and frame rate category requirements.
             {24_Hz, FrameRateCategory::High, 120_Hz},
@@ -1419,34 +1439,41 @@
     };
 
     for (auto testCase : testCases) {
+        std::vector<LayerRequirement> layers;
         ALOGI("**** %s: Testing desiredFrameRate=%s, frameRateCategory=%s", __func__,
               to_string(testCase.desiredFrameRate).c_str(),
               ftl::enum_string(testCase.frameRateCategory).c_str());
 
-        lr.desiredRefreshRate = testCase.desiredFrameRate;
-
-        std::stringstream ss;
-        ss << to_string(testCase.desiredFrameRate)
-           << ", category=" << ftl::enum_string(testCase.frameRateCategory);
-        lr.name = ss.str();
-
-        if (testCase.frameRateCategory != FrameRateCategory::Default) {
-            layers[1].frameRateCategory = testCase.frameRateCategory;
+        if (testCase.desiredFrameRate.isValid()) {
+            std::stringstream ss;
+            ss << to_string(testCase.desiredFrameRate) << "ExplicitDefault";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitDefault,
+                                      .desiredRefreshRate = testCase.desiredFrameRate,
+                                      .weight = 1.f};
+            layers.push_back(layer);
         }
 
-        EXPECT_EQ(testCase.expectedFrameRate, selector.getBestFrameRateMode(layers)->getFps())
-                << "did not get expected frame rate for " << lr.name;
+        if (testCase.frameRateCategory != FrameRateCategory::Default) {
+            std::stringstream ss;
+            ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory) << ")";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitCategory,
+                                      .frameRateCategory = testCase.frameRateCategory,
+                                      .weight = 1.f};
+            layers.push_back(layer);
+        }
+
+        EXPECT_EQ(testCase.expectedFrameRate, selector.getBestFrameRateMode(layers)->getPeakFps())
+                << "Did not get expected frame rate for frameRate="
+                << to_string(testCase.desiredFrameRate)
+                << " category=" << ftl::enum_string(testCase.frameRateCategory);
     }
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_60_120) {
     auto selector = createSelector(makeModes(kMode60, kMode120), kModeId60);
 
-    std::vector<LayerRequirement> layers = {{.vote = LayerVoteType::ExplicitDefault, .weight = 1.f},
-                                            {.vote = LayerVoteType::ExplicitCategory,
-                                             .weight = 1.f}};
-    auto& lr = layers[0];
-
     struct Case {
         // Params
         Fps desiredFrameRate = 0_Hz;
@@ -1476,23 +1503,35 @@
     };
 
     for (auto testCase : testCases) {
+        std::vector<LayerRequirement> layers;
         ALOGI("**** %s: Testing desiredFrameRate=%s, frameRateCategory=%s", __func__,
               to_string(testCase.desiredFrameRate).c_str(),
               ftl::enum_string(testCase.frameRateCategory).c_str());
 
-        lr.desiredRefreshRate = testCase.desiredFrameRate;
-
-        std::stringstream ss;
-        ss << to_string(testCase.desiredFrameRate)
-           << ", category=" << ftl::enum_string(testCase.frameRateCategory);
-        lr.name = ss.str();
-
-        if (testCase.frameRateCategory != FrameRateCategory::Default) {
-            layers[1].frameRateCategory = testCase.frameRateCategory;
+        if (testCase.desiredFrameRate.isValid()) {
+            std::stringstream ss;
+            ss << to_string(testCase.desiredFrameRate) << "ExplicitDefault";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitDefault,
+                                      .desiredRefreshRate = testCase.desiredFrameRate,
+                                      .weight = 1.f};
+            layers.push_back(layer);
         }
 
-        EXPECT_EQ(testCase.expectedFrameRate, selector.getBestFrameRateMode(layers)->getFps())
-                << "did not get expected frame rate for " << lr.name;
+        if (testCase.frameRateCategory != FrameRateCategory::Default) {
+            std::stringstream ss;
+            ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory) << ")";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitCategory,
+                                      .frameRateCategory = testCase.frameRateCategory,
+                                      .weight = 1.f};
+            layers.push_back(layer);
+        }
+
+        EXPECT_EQ(testCase.expectedFrameRate, selector.getBestFrameRateMode(layers)->getPeakFps())
+                << "Did not get expected frame rate for frameRate="
+                << to_string(testCase.desiredFrameRate)
+                << " category=" << ftl::enum_string(testCase.frameRateCategory);
     }
 }
 
@@ -1529,9 +1568,10 @@
         ss << "ExplicitDefault " << desired;
         lr.name = ss.str();
 
-        const auto bestFps = selector.getBestFrameRateMode(layers)->getFps();
-        EXPECT_EQ(expected, bestFps)
-                << "expected " << expected << " for " << desired << " but got " << bestFps;
+        const auto bestMode = selector.getBestFrameRateMode(layers);
+        EXPECT_EQ(expected, bestMode->getPeakFps())
+                << "expected " << expected << " for " << desired << " but got "
+                << bestMode->getPeakFps() << "(" << bestMode->getVsyncRate() << ")";
     }
 }
 
@@ -1608,7 +1648,7 @@
             ss << "ExplicitExact " << desired;
             lr.name = ss.str();
 
-            EXPECT_EQ(lr.desiredRefreshRate, selector.getBestFrameRateMode(layers)->getFps());
+            EXPECT_EQ(lr.desiredRefreshRate, selector.getBestFrameRateMode(layers)->getPeakFps());
         }
     }
 }
@@ -1713,9 +1753,9 @@
     for (size_t i = 0; i < expectedRanking.size(); ++i) {
         EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
                 << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
-                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRanking[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
-                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << actualRanking[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 
     lr1.vote = LayerVoteType::Max;
@@ -1755,9 +1795,9 @@
     for (size_t i = 0; i < expectedRanking.size(); ++i) {
         EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
                 << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
-                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRanking[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
-                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << actualRanking[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 
     lr1.vote = LayerVoteType::Heuristic;
@@ -1795,9 +1835,9 @@
     for (size_t i = 0; i < expectedRanking.size(); ++i) {
         EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
                 << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
-                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRanking[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
-                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << actualRanking[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 
     lr1.desiredRefreshRate = 120_Hz;
@@ -1838,9 +1878,9 @@
     for (size_t i = 0; i < expectedRanking.size(); ++i) {
         EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
                 << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
-                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << expectedRanking[i].modePtr->getVsyncRate().getIntValue() << ")"
                 << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
-                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+                << actualRanking[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
     }
 }
 
@@ -2485,7 +2525,8 @@
     const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) {
         layers[0].desiredRefreshRate = fps;
         layers[0].vote = vote;
-        EXPECT_EQ(fps.getIntValue(), selector.getBestFrameRateMode(layers)->getFps().getIntValue())
+        EXPECT_EQ(fps.getIntValue(),
+                  selector.getBestFrameRateMode(layers)->getPeakFps().getIntValue())
                 << "Failed for " << ftl::enum_string(vote);
     };
 
@@ -2524,13 +2565,13 @@
             },
     };
 
-    EXPECT_EQ(53_Hz, selector.getBestFrameRateMode(layers, globalSignals)->getFps());
+    EXPECT_EQ(53_Hz, selector.getBestFrameRateMode(layers, globalSignals)->getPeakFps());
 }
 
 TEST_P(RefreshRateSelectorTest, modeComparison) {
-    EXPECT_LT(kMode60->getFps(), kMode90->getFps());
-    EXPECT_GE(kMode60->getFps(), kMode60->getFps());
-    EXPECT_GE(kMode90->getFps(), kMode90->getFps());
+    EXPECT_LT(kMode60->getPeakFps(), kMode90->getPeakFps());
+    EXPECT_GE(kMode60->getPeakFps(), kMode60->getPeakFps());
+    EXPECT_GE(kMode90->getPeakFps(), kMode90->getPeakFps());
 }
 
 TEST_P(RefreshRateSelectorTest, testKernelIdleTimerAction) {
@@ -2579,27 +2620,27 @@
     auto selector = createSelector(kModes_30_60_72_90_120, kModeId30);
 
     const auto frameRate = 30_Hz;
-    Fps displayRefreshRate = selector.getActiveMode().getFps();
+    Fps displayRefreshRate = selector.getActiveMode().getPeakFps();
     EXPECT_EQ(1, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
 
     selector.setActiveMode(kModeId60, 60_Hz);
-    displayRefreshRate = selector.getActiveMode().getFps();
+    displayRefreshRate = selector.getActiveMode().getPeakFps();
     EXPECT_EQ(2, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
 
     selector.setActiveMode(kModeId72, 72_Hz);
-    displayRefreshRate = selector.getActiveMode().getFps();
+    displayRefreshRate = selector.getActiveMode().getPeakFps();
     EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
 
     selector.setActiveMode(kModeId90, 90_Hz);
-    displayRefreshRate = selector.getActiveMode().getFps();
+    displayRefreshRate = selector.getActiveMode().getPeakFps();
     EXPECT_EQ(3, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
 
     selector.setActiveMode(kModeId120, 120_Hz);
-    displayRefreshRate = selector.getActiveMode().getFps();
+    displayRefreshRate = selector.getActiveMode().getPeakFps();
     EXPECT_EQ(4, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
 
     selector.setActiveMode(kModeId90, 90_Hz);
-    displayRefreshRate = selector.getActiveMode().getFps();
+    displayRefreshRate = selector.getActiveMode().getPeakFps();
     EXPECT_EQ(4, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, 22.5_Hz));
 
     EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(24_Hz, 25_Hz));
@@ -3029,7 +3070,7 @@
     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());
+        EXPECT_EQ(expectedRefreshRate, primaryRefreshRates[i].modePtr->getPeakFps());
     }
 }
 
@@ -3175,6 +3216,7 @@
 }
 
 // TODO(b/266481656): Once this bug is fixed, we can remove this test
+// And test for VRR when we remove this work around for VRR.
 TEST_P(RefreshRateSelectorTest, noLowerFrameRateOnMinVote) {
     auto selector = createSelector(kModes_60_90, kModeId60);
 
@@ -3304,5 +3346,187 @@
     EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
 }
 
+// VRR tests
+TEST_P(RefreshRateSelectorTest, singleMinMaxRateForVrr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled || !flags::vrr_config()) {
+        return;
+    }
+
+    auto selector = createSelector(kVrrMode_120, kModeId120);
+    EXPECT_TRUE(selector.supportsFrameRateOverride());
+
+    const auto minRate = selector.getMinSupportedRefreshRate();
+    const auto performanceRate = selector.getMaxSupportedRefreshRate();
+    const auto minRateByPolicy = selector.getMinRefreshRateByPolicy();
+    const auto performanceRateByPolicy = selector.getMaxRefreshRateByPolicy();
+
+    EXPECT_EQ(kVrrMode120TE240, minRate);
+    EXPECT_EQ(kVrrMode120TE240, performanceRate);
+    EXPECT_EQ(kVrrMode120TE240, minRateByPolicy);
+    EXPECT_EQ(kVrrMode120TE240, performanceRateByPolicy);
+}
+
+TEST_P(RefreshRateSelectorTest, renderRateChangesWithPolicyChangeForVrr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled || !flags::vrr_config()) {
+        return;
+    }
+
+    auto selector = createSelector(kVrrModes_60_120, kModeId120);
+
+    const FpsRange only120 = {120_Hz, 120_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {only120, only120}, {only120, only120}}));
+    EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 120_Hz,
+                           selector.getBestScoredFrameRate({}).frameRateMode);
+
+    const FpsRange range120 = {0_Hz, 120_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {only120, range120}, {only120, range120}}));
+    EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 120_Hz,
+                           selector.getBestScoredFrameRate({}).frameRateMode);
+
+    const FpsRange range90 = {0_Hz, 90_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {only120, range90}, {only120, range90}}));
+    EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 80_Hz,
+                           selector.getBestScoredFrameRate({}).frameRateMode);
+
+    const FpsRange range80 = {0_Hz, 80_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {only120, range80}, {only120, range80}}));
+    EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 80_Hz,
+                           selector.getBestScoredFrameRate({}).frameRateMode);
+
+    const FpsRange range60 = {0_Hz, 60_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {only120, range60}, {only120, range60}}));
+    EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 60_Hz,
+                           selector.getBestScoredFrameRate({}).frameRateMode);
+
+    const FpsRange range48 = {0_Hz, 48_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {only120, range48}, {only120, range48}}));
+    EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 48_Hz,
+                           selector.getBestScoredFrameRate({}).frameRateMode);
+
+    const FpsRange range30 = {0_Hz, 30_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {only120, range30}, {only120, range30}}));
+    EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 30_Hz,
+                           selector.getBestScoredFrameRate({}).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, modeChangesWithPolicyChangeForVrr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled || !flags::vrr_config()) {
+        return;
+    }
+
+    auto selector = createSelector(kVrrModes_60_120, kModeId120);
+
+    const FpsRange range120 = {0_Hz, 120_Hz};
+    const FpsRange range60 = {0_Hz, 60_Hz};
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {range120, range60}, {range120, range60}}));
+    EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 60_Hz,
+                           selector.getBestScoredFrameRate({}).frameRateMode);
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId60, {range60, range60}, {range60, range60}}));
+    EXPECT_FRAME_RATE_MODE(kVrrMode60TE120, 60_Hz,
+                           selector.getBestScoredFrameRate({}).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverridesForVrr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled || !flags::vrr_config()) {
+        return;
+    }
+
+    auto selector = createSelector(kVrrMode_120, kModeId120);
+    // TODO(b/297600226) Run at lower than 30 Fps for dVRR
+    const std::vector<Fps> desiredRefreshRates = {30_Hz, 34.285_Hz, 40_Hz, 48_Hz,
+                                                  60_Hz, 80_Hz,     120_Hz};
+    const std::vector<LayerVoteType> layerVotes = {LayerVoteType::ExplicitDefault,
+                                                   LayerVoteType::ExplicitExactOrMultiple,
+                                                   LayerVoteType::ExplicitExact};
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].name = "Test layer";
+    layers[0].ownerUid = 1234;
+
+    for (auto desiredRefreshRate : desiredRefreshRates) {
+        layers[0].desiredRefreshRate = desiredRefreshRate;
+        for (auto vote : layerVotes) {
+            layers[0].vote = vote;
+            auto frameRateOverrides = selector.getFrameRateOverrides(layers, 240_Hz, {});
+            EXPECT_EQ(1u, frameRateOverrides.size());
+            ASSERT_EQ(1u, frameRateOverrides.count(1234));
+            EXPECT_EQ(desiredRefreshRate, frameRateOverrides.at(1234));
+        }
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, renderFrameRatesForVrr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled || !flags::vrr_config()) {
+        return;
+    }
+
+    auto selector = createSelector(kVrrMode_120, kModeId120);
+    const FpsRange only120 = {120_Hz, 120_Hz};
+    const FpsRange range120 = {0_Hz, 120_Hz};
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {only120, range120}, {only120, range120}}));
+
+    std::vector<Fps> expected = {20_Hz, 21.818_Hz, 24_Hz, 26.666_Hz, 30_Hz, 34.285_Hz,
+                                 40_Hz, 48_Hz,     60_Hz, 80_Hz,     120_Hz};
+
+    auto primaryRefreshRates = selector.getPrimaryFrameRates();
+    ASSERT_EQ(expected.size(), primaryRefreshRates.size());
+
+    for (size_t i = 0; i < expected.size(); i++) {
+        EXPECT_EQ(expected[i], primaryRefreshRates[i].fps);
+        EXPECT_EQ(120_Hz, primaryRefreshRates[i].modePtr->getPeakFps());
+    }
+
+    // Render range (0,90)
+    const FpsRange range90 = {0_Hz, 90_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {range120, range90}, {range120, range90}}));
+
+    expected = {20_Hz, 21.818_Hz, 24_Hz, 26.666_Hz, 30_Hz, 34.285_Hz, 40_Hz, 48_Hz, 60_Hz, 80_Hz};
+
+    primaryRefreshRates = selector.getPrimaryFrameRates();
+    ASSERT_EQ(expected.size(), primaryRefreshRates.size());
+    for (size_t i = 0; i < expected.size(); i++) {
+        EXPECT_EQ(expected[i], primaryRefreshRates[i].fps);
+        EXPECT_EQ(120_Hz, primaryRefreshRates[i].modePtr->getPeakFps());
+    }
+
+    // Render range (0,60)
+    const FpsRange range60 = {0_Hz, 60_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy(
+                      {kModeId120, {range120, range60}, {range120, range60}}));
+    expected = {20_Hz, 21.818_Hz, 24_Hz, 26.666_Hz, 30_Hz, 34.285_Hz, 40_Hz, 48_Hz, 60_Hz};
+
+    primaryRefreshRates = selector.getPrimaryFrameRates();
+    ASSERT_EQ(expected.size(), primaryRefreshRates.size());
+    for (size_t i = 0; i < expected.size(); i++) {
+        EXPECT_EQ(expected[i], primaryRefreshRates[i].fps);
+        EXPECT_EQ(120_Hz, primaryRefreshRates[i].modePtr->getPeakFps());
+    }
+}
 } // namespace
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
index 608fa76..9899d42 100644
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
@@ -19,6 +19,7 @@
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <gui/FrameRateUtils.h>
 #include <gui/LayerMetadata.h>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
@@ -325,48 +326,6 @@
                                          std::make_shared<EffectLayerFactory>()),
                          PrintToStringParamName);
 
-TEST_F(SetFrameRateTest, ValidateFrameRate) {
-    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
-                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
-                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
-                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS, ""));
-    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
-                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-
-    // Privileged APIs.
-    EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
-                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-    EXPECT_FALSE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
-                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-
-    constexpr bool kPrivileged = true;
-    EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
-                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "",
-                                  kPrivileged));
-    EXPECT_TRUE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
-                                  ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "",
-                                  kPrivileged));
-
-    // Invalid frame rate.
-    EXPECT_FALSE(ValidateFrameRate(-1, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
-                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-    EXPECT_FALSE(ValidateFrameRate(1.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
-                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-    EXPECT_FALSE(ValidateFrameRate(0.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
-                                   ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-
-    // Invalid compatibility.
-    EXPECT_FALSE(
-            ValidateFrameRate(60.0f, -1, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-    EXPECT_FALSE(ValidateFrameRate(60.0f, 2, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-
-    // Invalid change frame rate strategy.
-    EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, -1, ""));
-    EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, 2, ""));
-}
-
 TEST_P(SetFrameRateTest, SetOnParentActivatesTree) {
     const auto& layerFactory = GetParam();
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 24eb318..3a5fdf9 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -476,6 +476,37 @@
 }
 
 TEST_F(DisplayModeSwitchingTest, powerOffDuringModeSet) {
+    EXPECT_TRUE(mDisplay->isPoweredOn());
+    EXPECT_THAT(mDisplay, ModeSettledTo(kModeId60));
+
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+                                                  mock::createDisplayModeSpecs(kModeId90.value(),
+                                                                               false, 0.f, 120.f)));
+
+    EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
+
+    // Power off the display before the mode has been set.
+    mDisplay->setPowerMode(hal::PowerMode::OFF);
+
+    const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
+    EXPECT_CALL(*mComposer,
+                setActiveConfigWithConstraints(kInnerDisplayHwcId,
+                                               hal::HWConfigId(kModeId90.value()), _, _))
+            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+
+    mFlinger.commit();
+
+    // Powering off should not abort the mode set.
+    EXPECT_FALSE(mDisplay->isPoweredOn());
+    EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
+
+    mFlinger.commit();
+
+    EXPECT_THAT(mDisplay, ModeSettledTo(kModeId90));
+}
+
+TEST_F(DisplayModeSwitchingTest, powerOffDuringConcurrentModeSet) {
     const auto [innerDisplay, outerDisplay] = injectOuterDisplay();
 
     EXPECT_TRUE(innerDisplay->isPoweredOn());
@@ -516,6 +547,7 @@
 
     mFlinger.commit();
 
+    // Powering off the inactive display should abort the mode set.
     EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
     EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
 
@@ -523,6 +555,28 @@
 
     EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
     EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+
+    innerDisplay->setPowerMode(hal::PowerMode::OFF);
+    outerDisplay->setPowerMode(hal::PowerMode::ON);
+
+    // Only the outer display is powered on.
+    mFlinger.onActiveDisplayChanged(innerDisplay.get(), *outerDisplay);
+
+    EXPECT_CALL(*mComposer,
+                setActiveConfigWithConstraints(kOuterDisplayHwcId,
+                                               hal::HWConfigId(kModeId60.value()), _, _))
+            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+
+    mFlinger.commit();
+
+    // The mode set should resume once the display becomes active.
+    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
+
+    mFlinger.commit();
+
+    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60));
 }
 
 } // namespace
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 8458aa3..908c9ab 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -997,7 +997,7 @@
 
                 const auto activeMode = modes.get(activeModeId);
                 LOG_ALWAYS_FATAL_IF(!activeMode);
-                const auto fps = activeMode->get()->getFps();
+                const auto fps = activeMode->get()->getPeakFps();
 
                 state.physical = {.id = physicalId,
                                   .hwcDisplayId = *mHwcDisplayId,
diff --git a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp
index 108151e..1cf14ae 100644
--- a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp
@@ -127,7 +127,7 @@
     sp<NativeHandle> stream =
             NativeHandle::create(reinterpret_cast<native_handle_t*>(DEFAULT_SIDEBAND_STREAM),
                                  false);
-    layer->setSidebandStream(stream);
+    layer->setSidebandStream(stream, FrameTimelineInfo{}, 20);
     mFlinger.mutableCurrentState().layersSortedByZ.add(layer);
     mTunnelModeEnabledReporter->updateTunnelModeStatus();
     mTunnelModeEnabledReporter->addListener(mTunnelModeEnabledListener);
@@ -151,7 +151,7 @@
     sp<NativeHandle> stream =
             NativeHandle::create(reinterpret_cast<native_handle_t*>(DEFAULT_SIDEBAND_STREAM),
                                  false);
-    layerWithSidebandStream->setSidebandStream(stream);
+    layerWithSidebandStream->setSidebandStream(stream, FrameTimelineInfo{}, 20);
 
     mFlinger.mutableCurrentState().layersSortedByZ.add(simpleLayer);
     mFlinger.mutableCurrentState().layersSortedByZ.add(layerWithSidebandStream);
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
index 3b36361..cb05c00 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
@@ -20,17 +20,23 @@
 
 namespace android::mock {
 
-inline DisplayModePtr createDisplayMode(
-        DisplayModeId modeId, Fps refreshRate, int32_t group = 0,
+inline DisplayMode::Builder createDisplayModeBuilder(
+        DisplayModeId modeId, Fps displayRefreshRate, int32_t group = 0,
         ui::Size resolution = ui::Size(1920, 1080),
         PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(0)) {
     return DisplayMode::Builder(hal::HWConfigId(modeId.value()))
             .setId(modeId)
             .setPhysicalDisplayId(displayId)
-            .setVsyncPeriod(refreshRate.getPeriodNsecs())
+            .setVsyncPeriod(displayRefreshRate.getPeriodNsecs())
             .setGroup(group)
-            .setResolution(resolution)
-            .build();
+            .setResolution(resolution);
+}
+
+inline DisplayModePtr createDisplayMode(
+        DisplayModeId modeId, Fps refreshRate, int32_t group = 0,
+        ui::Size resolution = ui::Size(1920, 1080),
+        PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(0)) {
+    return createDisplayModeBuilder(modeId, refreshRate, group, resolution, displayId).build();
 }
 
 inline DisplayModePtr createDisplayMode(PhysicalDisplayId displayId, DisplayModeId modeId,
@@ -38,11 +44,19 @@
     return createDisplayMode(modeId, refreshRate, {}, {}, displayId);
 }
 
+inline DisplayModePtr createVrrDisplayMode(
+        DisplayModeId modeId, Fps displayRefreshRate, hal::VrrConfig vrrConfig, int32_t group = 0,
+        ui::Size resolution = ui::Size(1920, 1080),
+        PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(0)) {
+    return createDisplayModeBuilder(modeId, displayRefreshRate, group, resolution, displayId)
+            .setVrrConfig(std::move(vrrConfig))
+            .build();
+}
 inline DisplayModePtr cloneForDisplay(PhysicalDisplayId displayId, const DisplayModePtr& modePtr) {
     return DisplayMode::Builder(modePtr->getHwcId())
             .setId(modePtr->getId())
             .setPhysicalDisplayId(displayId)
-            .setVsyncPeriod(modePtr->getVsyncPeriod())
+            .setVsyncPeriod(modePtr->getVsyncRate().getPeriodNsecs())
             .setGroup(modePtr->getGroup())
             .setResolution(modePtr->getResolution())
             .build();
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index 9a1a16d..8e782eb 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -33,6 +33,7 @@
                 (ResyncCallback, EventRegistrationFlags), (const, override));
     MOCK_METHOD(void, enableSyntheticVsync, (bool), (override));
     MOCK_METHOD(void, onHotplugReceived, (PhysicalDisplayId, bool), (override));
+    MOCK_METHOD(void, onHotplugConnectionError, (int32_t), (override));
     MOCK_METHOD(void, onModeChanged, (const scheduler::FrameRateMode&), (override));
     MOCK_METHOD(void, onFrameRateOverridesChanged,
                 (PhysicalDisplayId, std::vector<FrameRateOverride>), (override));
diff --git a/services/surfaceflinger/tests/unittests/mock/MockFrameRateMode.h b/services/surfaceflinger/tests/unittests/mock/MockFrameRateMode.h
index 4cfdd58..bfcdd9b 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockFrameRateMode.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockFrameRateMode.h
@@ -19,7 +19,7 @@
 #include <scheduler/FrameRateMode.h>
 
 // Use a C style macro to keep the line numbers printed in gtest
-#define EXPECT_FRAME_RATE_MODE(_modePtr, _fps, _mode)                                \
-    EXPECT_EQ((scheduler::FrameRateMode{(_fps), (_modePtr)}), (_mode))               \
-            << "Expected " << (_fps) << " (" << (_modePtr)->getFps() << ") but was " \
-            << (_mode).fps << " (" << (_mode).modePtr->getFps() << ")"
+#define EXPECT_FRAME_RATE_MODE(_modePtr, _fps, _mode)                                      \
+    EXPECT_EQ((scheduler::FrameRateMode{(_fps), (_modePtr)}), (_mode))                     \
+            << "Expected " << (_fps) << " (" << (_modePtr)->getVsyncRate() << ") but was " \
+            << (_mode).fps << " (" << (_mode).modePtr->getVsyncRate() << ")"
diff --git a/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h b/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h
index 8e28a75..11723c7 100644
--- a/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h
+++ b/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <android-base/properties.h>
 #include <gtest/gtest.h>
 #include <gui/SurfaceComposerClient.h>
 #include <private/android_filesystem_config.h>
@@ -21,7 +22,8 @@
 #include <future>
 
 namespace android {
-using Transaction = SurfaceComposerClient::Transaction;
+
+using base::HwTimeoutMultiplier;
 using gui::DisplayInfo;
 using gui::WindowInfo;
 
@@ -36,7 +38,8 @@
         auto listener = sp<WindowInfosListener>::make(std::move(predicate), promise);
         mClient->addWindowInfosListener(listener);
         auto future = promise.get_future();
-        bool satisfied = future.wait_for(std::chrono::seconds{1}) == std::future_status::ready;
+        bool satisfied = future.wait_for(std::chrono::seconds{5 * HwTimeoutMultiplier()}) ==
+                std::future_status::ready;
         mClient->removeWindowInfosListener(listener);
         return satisfied;
     }
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 7159d83..c5870d4 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -1565,22 +1565,26 @@
               query_value);
         return VK_ERROR_SURFACE_LOST_KHR;
     }
-    uint32_t min_undequeued_buffers = static_cast<uint32_t>(query_value);
-    const auto mailbox_num_images = std::max(3u, create_info->minImageCount);
-    const auto requested_images =
-        swap_interval ? create_info->minImageCount : mailbox_num_images;
-    uint32_t num_images = requested_images - 1 + min_undequeued_buffers;
+    const uint32_t min_undequeued_buffers = static_cast<uint32_t>(query_value);
 
     // Lower layer insists that we have at least min_undequeued_buffers + 1
     // buffers.  This is wasteful and we'd like to relax it in the shared case,
     // but not all the pieces are in place for that to work yet.  Note we only
     // lie to the lower layer--we don't want to give the app back a swapchain
     // with extra images (which they can't actually use!).
-    uint32_t min_buffer_count = min_undequeued_buffers + 1;
-    err = native_window_set_buffer_count(
-        window, std::max(min_buffer_count, num_images));
+    const uint32_t min_buffer_count = min_undequeued_buffers + 1;
+
+    uint32_t num_images;
+    if (create_info->presentMode  == VK_PRESENT_MODE_MAILBOX_KHR) {
+        num_images = std::max(3u, create_info->minImageCount);
+    } else {
+        num_images = create_info->minImageCount;
+    }
+
+    const uint32_t buffer_count = std::max(min_buffer_count, num_images);
+    err = native_window_set_buffer_count(window, buffer_count);
     if (err != android::OK) {
-        ALOGE("native_window_set_buffer_count(%d) failed: %s (%d)", num_images,
+        ALOGE("native_window_set_buffer_count(%d) failed: %s (%d)", buffer_count,
               strerror(-err), err);
         return VK_ERROR_SURFACE_LOST_KHR;
     }
diff --git a/vulkan/vkjson/Android.bp b/vulkan/vkjson/Android.bp
index b544245..de4271d 100644
--- a/vulkan/vkjson/Android.bp
+++ b/vulkan/vkjson/Android.bp
@@ -7,8 +7,19 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
-cc_library_shared {
+cc_defaults {
+    name: "libvkjson_deps",
+    shared_libs: [
+        "libjsoncpp",
+        "libvulkan",
+    ],
+}
+
+cc_library_static {
     name: "libvkjson",
+    defaults: [
+        "libvkjson_deps",
+    ],
     srcs: [
         "vkjson.cc",
         "vkjson_instance.cc",
@@ -24,10 +35,6 @@
     export_include_dirs: [
         ".",
     ],
-    shared_libs: [
-        "libjsoncpp",
-        "libvulkan",
-    ],
     export_shared_lib_headers: [
         "libvulkan",
     ],