Merge "Removing getSystemPreferredDisplayMode and adding it as part of DynamicDisplayInfo"
diff --git a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
index 0712c0a..28e5ee2 100644
--- a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
@@ -240,11 +240,11 @@
     EXPECT_GE(st.st_size, 1000000 /* 1MB */);
 }
 
-TEST_F(ZippedBugreportGenerationTest, TakesBetween30And300Seconds) {
-    EXPECT_GE(duration, 30s) << "Expected completion in more than 30s. Actual time "
-                             << duration.count() << " s.";
+TEST_F(ZippedBugreportGenerationTest, TakesBetween20And300Seconds) {
+    EXPECT_GE(duration, 20s) << "Expected completion in more than 20s. Actual time "
+                             << duration.count() << " ms.";
     EXPECT_LE(duration, 300s) << "Expected completion in less than 300s. Actual time "
-                              << duration.count() << " s.";
+                              << duration.count() << " ms.";
 }
 
 /**
diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp
index b6f42ad..c796da6 100644
--- a/cmds/installd/dexopt.cpp
+++ b/cmds/installd/dexopt.cpp
@@ -244,7 +244,7 @@
 // The location is the profile name for primary apks or the dex path for secondary dex files.
 bool clear_primary_current_profiles(const std::string& package_name, const std::string& location) {
     bool success = true;
-    // For secondary dex files, we don't really need the user but we use it for sanity checks.
+    // For secondary dex files, we don't really need the user but we use it for validity checks.
     std::vector<userid_t> users = get_known_users(/*volume_uuid*/ nullptr);
     for (auto user : users) {
         success &= clear_current_profile(package_name, location, user, /*is_secondary_dex*/false);
@@ -468,7 +468,7 @@
     *reference_profile_fd = open_reference_profile(uid, package_name, location,
             /*read_write*/ true, is_secondary_dex);
 
-    // For secondary dex files, we don't really need the user but we use it for sanity checks.
+    // For secondary dex files, we don't really need the user but we use it for validity checks.
     // Note: the user owning the dex file should be the current user.
     std::vector<userid_t> users;
     if (is_secondary_dex){
diff --git a/cmds/installd/utils.cpp b/cmds/installd/utils.cpp
index c4ecd07..0f8a732 100644
--- a/cmds/installd/utils.cpp
+++ b/cmds/installd/utils.cpp
@@ -829,7 +829,7 @@
  * to top level directories (i.e. have "..").
  */
 static int validate_path(const std::string& dir, const std::string& path, int maxSubdirs) {
-    // Argument sanity checking
+    // Argument check
     if (dir.find('/') != 0 || dir.rfind('/') != dir.size() - 1
             || dir.find("..") != std::string::npos) {
         LOG(ERROR) << "Invalid directory " << dir;
diff --git a/cmds/ip-up-vpn/Android.bp b/cmds/ip-up-vpn/Android.bp
new file mode 100644
index 0000000..c746f7f
--- /dev/null
+++ b/cmds/ip-up-vpn/Android.bp
@@ -0,0 +1,31 @@
+// Copyright 2011 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+    name: "ip-up-vpn",
+
+    srcs: ["ip-up-vpn.c"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    shared_libs: [
+        "libcutils",
+        "liblog",
+    ],
+}
diff --git a/cmds/ip-up-vpn/Android.mk b/cmds/ip-up-vpn/Android.mk
deleted file mode 100644
index 396ae9d..0000000
--- a/cmds/ip-up-vpn/Android.mk
+++ /dev/null
@@ -1,30 +0,0 @@
-#
-# Copyright (C) 2011 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.
-#
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := ip-up-vpn.c
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_SHARED_LIBRARIES := libcutils liblog
-LOCAL_MODULE := ip-up-vpn
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/ppp
-LOCAL_MODULE_TAGS := optional
-
-include $(BUILD_EXECUTABLE)
diff --git a/cmds/service/service.cpp b/cmds/service/service.cpp
index fe417a3..d5ca725 100644
--- a/cmds/service/service.cpp
+++ b/cmds/service/service.cpp
@@ -21,6 +21,7 @@
 #include <cutils/ashmem.h>
 
 #include <getopt.h>
+#include <libgen.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
diff --git a/cmds/servicemanager/Android.bp b/cmds/servicemanager/Android.bp
index 80c0548..32922ca 100644
--- a/cmds/servicemanager/Android.bp
+++ b/cmds/servicemanager/Android.bp
@@ -47,6 +47,14 @@
 }
 
 cc_binary {
+    name: "servicemanager.microdroid",
+    defaults: ["servicemanager_defaults"],
+    init_rc: ["servicemanager.microdroid.rc"],
+    srcs: ["main.cpp"],
+    bootstrap: true,
+}
+
+cc_binary {
     name: "servicemanager.recovery",
     stem: "servicemanager",
     recovery: true,
diff --git a/cmds/servicemanager/servicemanager.microdroid.rc b/cmds/servicemanager/servicemanager.microdroid.rc
new file mode 100644
index 0000000..e01f132
--- /dev/null
+++ b/cmds/servicemanager/servicemanager.microdroid.rc
@@ -0,0 +1,8 @@
+service servicemanager /system/bin/servicemanager.microdroid
+    class core
+    user system
+    group system readproc
+    critical
+    onrestart restart apexd
+    task_profiles ServiceCapacityLow
+    shutdown critical
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 1be3a69..31dee23 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -245,6 +245,12 @@
 }
 
 prebuilt_etc {
+    name: "go_handheld_core_hardware.prebuilt.xml",
+    src: "go_handheld_core_hardware.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "handheld_core_hardware.prebuilt.xml",
     src: "handheld_core_hardware.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/data/etc/android.hardware.vulkan.version-1_3.xml b/data/etc/android.hardware.vulkan.version-1_3.xml
new file mode 100644
index 0000000..4ecea7b
--- /dev/null
+++ b/data/etc/android.hardware.vulkan.version-1_3.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- This is the standard feature indicating that the device has a Vulkan
+     driver that supports API version 1.3 (0x00403000) -->
+<permissions>
+    <feature name="android.hardware.vulkan.version" version="4206592" />
+</permissions>
diff --git a/headers/Android.bp b/headers/Android.bp
index 7481a23..cb18837 100644
--- a/headers/Android.bp
+++ b/headers/Android.bp
@@ -28,6 +28,11 @@
         "libstagefright_foundation_headers",
     ],
     min_sdk_version: "29",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media",
+        "com.android.media.swcodec",
+    ],
 
     host_supported: true,
     target: {
diff --git a/include/android/choreographer.h b/include/android/choreographer.h
index 6f579ca..98f0eec 100644
--- a/include/android/choreographer.h
+++ b/include/android/choreographer.h
@@ -39,6 +39,12 @@
  */
 typedef struct AChoreographer AChoreographer;
 
+
+/**
+ * The identifier of a frame timeline.
+ */
+typedef int64_t AVsyncId;
+
 struct AChoreographerFrameCallbackData;
 /**
  * Opaque type that provides access to an AChoreographerFrameCallbackData object.
@@ -203,7 +209,7 @@
 /**
  * The vsync ID token used to map Choreographer data.
  */
-int64_t AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
+AVsyncId AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
         const AChoreographerFrameCallbackData* data, size_t index) __INTRODUCED_IN(33);
 
 /**
diff --git a/include/android/input.h b/include/android/input.h
index fbd61b5..e6ad943f 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -877,6 +877,7 @@
  * Keyboard types.
  *
  * Refer to the documentation on android.view.InputDevice for more details.
+ * Note: When adding a new keyboard type here InputDeviceInfo::setKeyboardType needs to be updated.
  */
 enum {
     /** none */
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
new file mode 100644
index 0000000..5fa47f6
--- /dev/null
+++ b/include/android/performance_hint.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_NATIVE_PERFORMANCE_HINT_H
+#define ANDROID_NATIVE_PERFORMANCE_HINT_H
+
+#include <sys/cdefs.h>
+
+/******************************************************************
+ *
+ * IMPORTANT NOTICE:
+ *
+ *   This file is part of Android's set of stable system headers
+ *   exposed by the Android NDK (Native Development Kit).
+ *
+ *   Third-party source AND binary code relies on the definitions
+ *   here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES.
+ *
+ *   - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES)
+ *   - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS
+ *   - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY
+ *   - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES
+ */
+
+#include <android/api-level.h>
+#include <stdint.h>
+
+__BEGIN_DECLS
+
+struct APerformanceHintManager;
+struct APerformanceHintSession;
+
+/**
+ * An opaque type representing a handle to a performance hint manager.
+ * It must be released after use.
+ *
+ * <p>To use:<ul>
+ *    <li>Obtain the performance hint manager instance by calling
+ *        {@link APerformanceHint_getManager} function.</li>
+ *    <li>Create an {@link APerformanceHintSession} with
+ *        {@link APerformanceHint_createSession}.</li>
+ *    <li>Get the preferred update rate in nanoseconds with
+ *        {@link APerformanceHint_getPreferredUpdateRateNanos}.</li>
+ */
+typedef struct APerformanceHintManager APerformanceHintManager;
+
+/**
+ * An opaque type representing a handle to a performance hint session.
+ * A session can only be acquired from a {@link APerformanceHintManager}
+ * with {@link APerformanceHint_getPreferredUpdateRateNanos}. It must be
+ * freed with {@link APerformanceHint_closeSession} after use.
+ *
+ * A Session represents a group of threads with an inter-related workload such that hints for
+ * their performance should be considered as a unit. The threads in a given session should be
+ * long-life and not created or destroyed dynamically.
+ *
+ * <p>Each session is expected to have a periodic workload with a target duration for each
+ * cycle. The cycle duration is likely greater than the target work duration to allow other
+ * parts of the pipeline to run within the available budget. For example, a renderer thread may
+ * work at 60hz in order to produce frames at the display's frame but have a target work
+ * duration of only 6ms.</p>
+ *
+ * <p>After each cycle of work, the client is expected to use
+ * {@link APerformanceHint_reportActualWorkDuration} to report the actual time taken to
+ * complete.</p>
+ *
+ * <p>To use:<ul>
+ *    <li>Update a sessions target duration for each cycle of work
+ *        with  {@link APerformanceHint_updateTargetWorkDuration}.</li>
+ *    <li>Report the actual duration for the last cycle of work with
+ *        {@link APerformanceHint_reportActualWorkDuration}.</li>
+ *    <li>Release the session instance with
+ *        {@link APerformanceHint_closeSession}.</li></ul></p>
+ */
+typedef struct APerformanceHintSession APerformanceHintSession;
+
+/**
+  * Acquire an instance of the performance hint manager.
+  *
+  * @return manager instance on success, nullptr on failure.
+  */
+APerformanceHintManager* APerformanceHint_getManager() __INTRODUCED_IN(__ANDROID_API_T__);
+
+/**
+ * Creates a session for the given set of threads and sets their initial target work
+ * duration.
+ * @param manager The performance hint manager instance.
+ * @param threadIds The list of threads to be associated with this session. They must be part of
+ *     this app's thread group.
+ * @param size the size of threadIds.
+ * @param initialTargetWorkDurationNanos The desired duration in nanoseconds for the new session.
+ *     This must be positive.
+ * @return manager instance on success, nullptr on failure.
+ */
+APerformanceHintSession* APerformanceHint_createSession(
+        APerformanceHintManager* manager,
+        const int32_t* threadIds, size_t size,
+        int64_t initialTargetWorkDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
+
+/**
+ * Get preferred update rate information for this device.
+ *
+ * @param manager The performance hint manager instance.
+ * @return the preferred update rate supported by device software.
+ */
+int64_t APerformanceHint_getPreferredUpdateRateNanos(
+        APerformanceHintManager* manager) __INTRODUCED_IN(__ANDROID_API_T__);
+
+/**
+ * Updates this session's target duration for each cycle of work.
+ *
+ * @param session The performance hint session instance to update.
+ * @param targetDurationNanos the new desired duration in nanoseconds. This must be positive.
+ * @return 0 on success
+ *         EINVAL if targetDurationNanos is not positive.
+ *         EPIPE if communication with the system service has failed.
+ */
+int APerformanceHint_updateTargetWorkDuration(
+        APerformanceHintSession* session,
+        int64_t targetDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
+
+/**
+ * Reports the actual duration for the last cycle of work.
+ *
+ * <p>The system will attempt to adjust the core placement of the threads within the thread
+ * group and/or the frequency of the core on which they are run to bring the actual duration
+ * close to the target duration.</p>
+ *
+ * @param session The performance hint session instance to update.
+ * @param actualDurationNanos how long the thread group took to complete its last task in
+ *     nanoseconds. This must be positive.
+ * @return 0 on success
+ *         EINVAL if actualDurationNanos is not positive.
+ *         EPIPE if communication with the system service has failed.
+ */
+int APerformanceHint_reportActualWorkDuration(
+        APerformanceHintSession* session,
+        int64_t actualDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
+
+/**
+ * Release the performance hint manager pointer acquired via
+ * {@link APerformanceHint_createSession}.
+ *
+ * @param session The performance hint session instance to release.
+ */
+void APerformanceHint_closeSession(
+        APerformanceHintSession* session) __INTRODUCED_IN(__ANDROID_API_T__);
+
+__END_DECLS
+
+#endif // ANDROID_NATIVE_PERFORMANCE_HINT_H
diff --git a/include/android/surface_control.h b/include/android/surface_control.h
index 3a13104..9a36ecb 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -28,6 +28,7 @@
 
 #include <sys/cdefs.h>
 
+#include <android/choreographer.h>
 #include <android/data_space.h>
 #include <android/hardware_buffer.h>
 #include <android/hdr_metadata.h>
@@ -596,13 +597,23 @@
                                                __INTRODUCED_IN(31);
 
 /**
- * Sets the frame timeline to use.
+ * Sets the frame timeline to use in Surface Flinger.
+ *
+ * A frame timeline should be chosen based on what frame deadline the application
+ * can meet when rendering the frame and the application's desired present time.
+ * By setting a frame timeline, Surface Flinger tries to present the frame at the corresponding
+ * expected present time.
+ *
+ * To receive frame timelines, a callback must be posted to Choreographer using
+ * AChoreographer_postExtendedFrameCallback(). The \a vsnycId can then be extracted from the
+ * callback payload using AChoreographerFrameCallbackData_getFrameTimelineVsyncId().
  *
  * \param vsyncId The vsync ID received from AChoreographer, setting the frame's present target to
- * the corresponding expected present time and deadline from the frame to be rendered.
+ * the corresponding expected present time and deadline from the frame to be rendered. A stale or
+ * invalid value will be ignored.
  */
 void ASurfaceTransaction_setFrameTimeline(ASurfaceTransaction* transaction,
-                                          int64_t vsyncId) __INTRODUCED_IN(33);
+                                          AVsyncId vsyncId) __INTRODUCED_IN(33);
 
 __END_DECLS
 
diff --git a/include/ftl/Flags.h b/include/ftl/Flags.h
index ae70831..932af2d 100644
--- a/include/ftl/Flags.h
+++ b/include/ftl/Flags.h
@@ -209,12 +209,12 @@
 
 template <typename F, typename = std::enable_if_t<ftl::is_scoped_enum_v<F>>>
 inline Flags<F> operator~(F f) {
-    return static_cast<F>(~ftl::enum_cast(f));
+    return static_cast<F>(~ftl::to_underlying(f));
 }
 
 template <typename F, typename = std::enable_if_t<ftl::is_scoped_enum_v<F>>>
 Flags<F> operator|(F lhs, F rhs) {
-    return static_cast<F>(ftl::enum_cast(lhs) | ftl::enum_cast(rhs));
+    return static_cast<F>(ftl::to_underlying(lhs) | ftl::to_underlying(rhs));
 }
 
 } // namespace flag_operators
diff --git a/include/ftl/array_traits.h b/include/ftl/details/array_traits.h
similarity index 96%
rename from include/ftl/array_traits.h
rename to include/ftl/details/array_traits.h
index 1265fa1..16e63ec 100644
--- a/include/ftl/array_traits.h
+++ b/include/ftl/details/array_traits.h
@@ -21,9 +21,9 @@
 #include <new>
 #include <type_traits>
 
-#define FTL_ARRAY_TRAIT(T, U) using U = typename ArrayTraits<T>::U
+#define FTL_ARRAY_TRAIT(T, U) using U = typename details::ArrayTraits<T>::U
 
-namespace android::ftl {
+namespace android::ftl::details {
 
 template <typename T>
 struct ArrayTraits {
@@ -132,4 +132,4 @@
   }
 };
 
-}  // namespace android::ftl
+}  // namespace android::ftl::details
diff --git a/include/ftl/enum.h b/include/ftl/enum.h
index dfe3a09..5234c05 100644
--- a/include/ftl/enum.h
+++ b/include/ftl/enum.h
@@ -87,11 +87,13 @@
 
 // Shorthand for casting an enumerator to its integral value.
 //
+// TODO: Replace with std::to_underlying in C++23.
+//
 //   enum class E { A, B, C };
-//   static_assert(ftl::enum_cast(E::B) == 1);
+//   static_assert(ftl::to_underlying(E::B) == 1);
 //
 template <typename E>
-constexpr auto enum_cast(E v) {
+constexpr auto to_underlying(E v) {
   return static_cast<std::underlying_type_t<E>>(v);
 }
 
@@ -137,19 +139,19 @@
 
 template <typename E>
 struct enum_end<E, std::void_t<decltype(E::ftl_last)>> {
-  static constexpr E value = E{enum_cast(E::ftl_last) + 1};
+  static constexpr E value = E{to_underlying(E::ftl_last) + 1};
 };
 
 template <typename E>
 inline constexpr E enum_end_v = enum_end<E>::value;
 
 template <typename E>
-inline constexpr E enum_last_v = E{enum_cast(enum_end_v<E>) - 1};
+inline constexpr E enum_last_v = E{to_underlying(enum_end_v<E>) - 1};
 
 template <typename E>
 struct enum_size {
-  static constexpr auto kBegin = enum_cast(enum_begin_v<E>);
-  static constexpr auto kEnd = enum_cast(enum_end_v<E>);
+  static constexpr auto kBegin = to_underlying(enum_begin_v<E>);
+  static constexpr auto kEnd = to_underlying(enum_end_v<E>);
   static_assert(kBegin < kEnd, "Invalid range");
 
   static constexpr std::size_t value = kEnd - kBegin;
@@ -174,7 +176,7 @@
 
 template <typename E, template <E> class F, typename T, T... Vs>
 struct EnumRange<E, F, std::integer_sequence<T, Vs...>> {
-  static constexpr auto kBegin = enum_cast(enum_begin_v<E>);
+  static constexpr auto kBegin = to_underlying(enum_begin_v<E>);
   static constexpr auto kSize = enum_size_v<E>;
 
   using R = decltype(F<E{}>::value);
@@ -194,7 +196,7 @@
   using E = decltype(I);
   using U = std::underlying_type_t<E>;
 
-  static constexpr E V{U{1} << enum_cast(I)};
+  static constexpr E V{U{1} << to_underlying(I)};
   static constexpr auto value = ftl_enum<E, V>();
 };
 
@@ -237,10 +239,10 @@
 //
 template <typename E>
 constexpr std::optional<std::string_view> enum_name(E v) {
-  const auto value = enum_cast(v);
+  const auto value = to_underlying(v);
 
-  constexpr auto kBegin = enum_cast(enum_begin_v<E>);
-  constexpr auto kLast = enum_cast(enum_last_v<E>);
+  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::EnumName>{};
@@ -256,7 +258,7 @@
 //
 template <typename E>
 constexpr std::optional<std::string_view> flag_name(E v) {
-  const auto value = enum_cast(v);
+  const auto value = to_underlying(v);
 
   // TODO: Replace with std::popcount and std::countr_zero in C++20.
   if (__builtin_popcountl(value) != 1) return {};
@@ -277,7 +279,7 @@
   if (const auto name = enum_name(v)) {
     return std::string(*name);
   }
-  return to_string(enum_cast(v));
+  return to_string(to_underlying(v));
 }
 
 // Returns a stringified flag enumerator, or its integral value if not named.
@@ -293,7 +295,7 @@
     return std::string(*name);
   }
   constexpr auto radix = sizeof(E) == 1 ? Radix::kBin : Radix::kHex;
-  return to_string(enum_cast(v), radix);
+  return to_string(to_underlying(v), radix);
 }
 
 }  // namespace android::ftl
diff --git a/include/ftl/small_vector.h b/include/ftl/small_vector.h
index 65a9536..03587e3 100644
--- a/include/ftl/small_vector.h
+++ b/include/ftl/small_vector.h
@@ -16,7 +16,7 @@
 
 #pragma once
 
-#include <ftl/array_traits.h>
+#include <ftl/details/array_traits.h>
 #include <ftl/static_vector.h>
 
 #include <algorithm>
@@ -73,7 +73,7 @@
 //   assert(strings[2] == "???");
 //
 template <typename T, std::size_t N>
-class SmallVector final : ArrayTraits<T>, ArrayComparators<SmallVector> {
+class SmallVector final : details::ArrayTraits<T>, details::ArrayComparators<SmallVector> {
   using Static = StaticVector<T, N>;
   using Dynamic = SmallVector<T, 0>;
 
@@ -266,12 +266,12 @@
 
 // Partial specialization without static storage.
 template <typename T>
-class SmallVector<T, 0> final : ArrayTraits<T>,
-                                ArrayIterators<SmallVector<T, 0>, T>,
+class SmallVector<T, 0> final : details::ArrayTraits<T>,
+                                details::ArrayIterators<SmallVector<T, 0>, T>,
                                 std::vector<T> {
-  using ArrayTraits<T>::construct_at;
+  using details::ArrayTraits<T>::construct_at;
 
-  using Iter = ArrayIterators<SmallVector, T>;
+  using Iter = details::ArrayIterators<SmallVector, T>;
   using Impl = std::vector<T>;
 
   friend Iter;
diff --git a/include/ftl/static_vector.h b/include/ftl/static_vector.h
index cd7b92a..b7f8c29 100644
--- a/include/ftl/static_vector.h
+++ b/include/ftl/static_vector.h
@@ -16,7 +16,7 @@
 
 #pragma once
 
-#include <ftl/array_traits.h>
+#include <ftl/details/array_traits.h>
 #include <ftl/initializer_list.h>
 
 #include <algorithm>
@@ -73,14 +73,14 @@
 //   assert(strings[2] == "???");
 //
 template <typename T, std::size_t N>
-class StaticVector final : ArrayTraits<T>,
-                           ArrayIterators<StaticVector<T, N>, T>,
-                           ArrayComparators<StaticVector> {
+class StaticVector final : details::ArrayTraits<T>,
+                           details::ArrayIterators<StaticVector<T, N>, T>,
+                           details::ArrayComparators<StaticVector> {
   static_assert(N > 0);
 
-  using ArrayTraits<T>::construct_at;
+  using details::ArrayTraits<T>::construct_at;
 
-  using Iter = ArrayIterators<StaticVector, T>;
+  using Iter = details::ArrayIterators<StaticVector, T>;
   friend Iter;
 
   // There is ambiguity when constructing from two iterator-like elements like pointers:
diff --git a/include/input/Input.h b/include/input/Input.h
index f4147a0..55ebb90 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -164,7 +164,7 @@
  * (We want at least 10 but some touch controllers obstensibly configured for 10 pointers
  * will occasionally emit 11.  There is not much harm making this constant bigger.)
  */
-#define MAX_POINTERS 16
+static constexpr size_t MAX_POINTERS = 16;
 
 /*
  * Maximum number of samples supported per motion event.
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 22aae19..c4f03c9 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -235,7 +235,7 @@
     void addBatteryInfo(const InputDeviceBatteryInfo& info);
     void addLightInfo(const InputDeviceLightInfo& info);
 
-    inline void setKeyboardType(int32_t keyboardType) { mKeyboardType = keyboardType; }
+    void setKeyboardType(int32_t keyboardType);
     inline int32_t getKeyboardType() const { return mKeyboardType; }
 
     inline void setKeyCharacterMap(const std::shared_ptr<KeyCharacterMap> value) {
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index edcb615..5f9a37d 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -500,24 +500,6 @@
     status_t sendTimeline(int32_t inputEventId,
                           std::array<nsecs_t, GraphicsTimeline::SIZE> timeline);
 
-    /* Returns true if there is a deferred event waiting.
-     *
-     * Should be called after calling consume() to determine whether the consumer
-     * has a deferred event to be processed.  Deferred events are somewhat special in
-     * that they have already been removed from the input channel.  If the input channel
-     * becomes empty, the client may need to do extra work to ensure that it processes
-     * the deferred event despite the fact that the input channel's file descriptor
-     * is not readable.
-     *
-     * One option is simply to call consume() in a loop until it returns WOULD_BLOCK.
-     * This guarantees that all deferred events will be processed.
-     *
-     * Alternately, the caller can call hasDeferredEvent() to determine whether there is
-     * a deferred event waiting and then ensure that its event loop wakes up at least
-     * one more time to consume the deferred event.
-     */
-    bool hasDeferredEvent() const;
-
     /* Returns true if there is a pending batch.
      *
      * Should be called after calling consume() with consumeBatches == false to determine
diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h
index 5832bf4..f27f5f1 100644
--- a/include/private/performance_hint_private.h
+++ b/include/private/performance_hint_private.h
@@ -17,124 +17,8 @@
 #ifndef ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H
 #define ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H
 
-#include <stdint.h>
-
 __BEGIN_DECLS
 
-struct APerformanceHintManager;
-struct APerformanceHintSession;
-
-/**
- * An opaque type representing a handle to a performance hint manager.
- * It must be released after use.
- *
- * <p>To use:<ul>
- *    <li>Obtain the performance hint manager instance by calling
- *        {@link APerformanceHint_getManager} function.</li>
- *    <li>Create an {@link APerformanceHintSession} with
- *        {@link APerformanceHint_createSession}.</li>
- *    <li>Get the preferred update rate in nanoseconds with
- *        {@link APerformanceHint_getPreferredUpdateRateNanos}.</li>
- */
-typedef struct APerformanceHintManager APerformanceHintManager;
-
-/**
- * An opaque type representing a handle to a performance hint session.
- * A session can only be acquired from a {@link APerformanceHintManager}
- * with {@link APerformanceHint_getPreferredUpdateRateNanos}. It must be
- * freed with {@link APerformanceHint_closeSession} after use.
- *
- * A Session represents a group of threads with an inter-related workload such that hints for
- * their performance should be considered as a unit. The threads in a given session should be
- * long-life and not created or destroyed dynamically.
- *
- * <p>Each session is expected to have a periodic workload with a target duration for each
- * cycle. The cycle duration is likely greater than the target work duration to allow other
- * parts of the pipeline to run within the available budget. For example, a renderer thread may
- * work at 60hz in order to produce frames at the display's frame but have a target work
- * duration of only 6ms.</p>
- *
- * <p>After each cycle of work, the client is expected to use
- * {@link APerformanceHint_reportActualWorkDuration} to report the actual time taken to
- * complete.</p>
- *
- * <p>To use:<ul>
- *    <li>Update a sessions target duration for each cycle of work
- *        with  {@link APerformanceHint_updateTargetWorkDuration}.</li>
- *    <li>Report the actual duration for the last cycle of work with
- *        {@link APerformanceHint_reportActualWorkDuration}.</li>
- *    <li>Release the session instance with
- *        {@link APerformanceHint_closeSession}.</li></ul></p>
- */
-typedef struct APerformanceHintSession APerformanceHintSession;
-
-/**
-  * Acquire an instance of the performance hint manager.
-  *
-  * @return manager instance on success, nullptr on failure.
-  */
-APerformanceHintManager* APerformanceHint_getManager();
-
-/**
- * Creates a session for the given set of threads and sets their initial target work
- * duration.
- * @param manager The performance hint manager instance.
- * @param threadIds The list of threads to be associated with this session. They must be part of
- *     this app's thread group.
- * @param size the size of threadIds.
- * @param initialTargetWorkDurationNanos The desired duration in nanoseconds for the new session.
- *     This must be positive.
- * @return manager instance on success, nullptr on failure.
- */
-APerformanceHintSession* APerformanceHint_createSession(APerformanceHintManager* manager,
-                                                        const int32_t* threadIds, size_t size,
-                                                        int64_t initialTargetWorkDurationNanos);
-
-/**
- * Get preferred update rate information for this device.
- *
- * @param manager The performance hint manager instance.
- * @return the preferred update rate supported by device software.
- */
-int64_t APerformanceHint_getPreferredUpdateRateNanos(APerformanceHintManager* manager);
-
-/**
- * Updates this session's target duration for each cycle of work.
- *
- * @param session The performance hint session instance to update.
- * @param targetDurationNanos the new desired duration in nanoseconds. This must be positive.
- * @return 0 on success
- *         EINVAL if targetDurationNanos is not positive.
- *         EPIPE if communication with the system service has failed.
- */
-int APerformanceHint_updateTargetWorkDuration(APerformanceHintSession* session,
-                                               int64_t targetDurationNanos);
-
-/**
- * Reports the actual duration for the last cycle of work.
- *
- * <p>The system will attempt to adjust the core placement of the threads within the thread
- * group and/or the frequency of the core on which they are run to bring the actual duration
- * close to the target duration.</p>
- *
- * @param session The performance hint session instance to update.
- * @param actualDurationNanos how long the thread group took to complete its last task in
- *     nanoseconds. This must be positive.
- * @return 0 on success
- *         EINVAL if actualDurationNanos is not positive.
- *         EPIPE if communication with the system service has failed.
- */
-int APerformanceHint_reportActualWorkDuration(APerformanceHintSession* session,
-                                               int64_t actualDurationNanos);
-
-/**
- * Release the performance hint manager pointer acquired via
- * {@link APerformanceHint_createSession}.
- *
- * @param session The performance hint session instance to release.
- */
-void APerformanceHint_closeSession(APerformanceHintSession* session);
-
 /**
  * For testing only.
  */
diff --git a/libs/arect/Android.bp b/libs/arect/Android.bp
index bb40f51..41b3460 100644
--- a/libs/arect/Android.bp
+++ b/libs/arect/Android.bp
@@ -57,4 +57,11 @@
         },
     },
     min_sdk_version: "29",
+    // static link, so it won't straddle a module boundary at runtime.
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media",
+        "com.android.media.swcodec",
+    ],
+
 }
diff --git a/libs/binder/rust/Android.bp b/libs/binder/rust/Android.bp
index e4df98a..355b3b4 100644
--- a/libs/binder/rust/Android.bp
+++ b/libs/binder/rust/Android.bp
@@ -32,7 +32,7 @@
         "com.android.uwb",
         "com.android.virt",
     ],
-    min_sdk_version: "current",
+    min_sdk_version: "Tiramisu",
 }
 
 rust_library {
@@ -44,6 +44,7 @@
         "libtokio",
     ],
     host_supported: true,
+    vendor_available: true,
     target: {
         darwin: {
             enabled: false,
@@ -52,8 +53,10 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.compos",
+        "com.android.uwb",
         "com.android.virt",
     ],
+    min_sdk_version: "Tiramisu",
 }
 
 rust_library {
@@ -79,7 +82,7 @@
         "com.android.uwb",
         "com.android.virt",
     ],
-    min_sdk_version: "current",
+    min_sdk_version: "Tiramisu",
     lints: "none",
     clippy_lints: "none",
 }
@@ -137,7 +140,7 @@
         "com.android.uwb",
         "com.android.virt",
     ],
-    min_sdk_version: "current",
+    min_sdk_version: "Tiramisu",
 }
 
 // TODO(b/184872979): remove once the Rust API is created.
@@ -154,7 +157,7 @@
         "com.android.uwb",
         "com.android.virt",
     ],
-    min_sdk_version: "current",
+    min_sdk_version: "Tiramisu",
 }
 
 rust_test {
diff --git a/libs/binder/rust/binder_tokio/lib.rs b/libs/binder/rust/binder_tokio/lib.rs
index 47dcdc2..9dcef42 100644
--- a/libs/binder/rust/binder_tokio/lib.rs
+++ b/libs/binder/rust/binder_tokio/lib.rs
@@ -28,8 +28,8 @@
 //!
 //! [`Tokio`]: crate::Tokio
 
-use binder::public_api::{BinderAsyncPool, BoxFuture, Strong};
-use binder::{FromIBinder, StatusCode, BinderAsyncRuntime};
+use binder::{BinderAsyncPool, BoxFuture, FromIBinder, StatusCode, Strong};
+use binder::binder_impl::BinderAsyncRuntime;
 use std::future::Future;
 
 /// Retrieve an existing service for a particular interface, sleeping for a few
@@ -37,12 +37,12 @@
 pub async fn get_interface<T: FromIBinder + ?Sized + 'static>(name: &str) -> Result<Strong<T>, StatusCode> {
     if binder::is_handling_transaction() {
         // See comment in the BinderAsyncPool impl.
-        return binder::public_api::get_interface::<T>(name);
+        return binder::get_interface::<T>(name);
     }
 
     let name = name.to_string();
     let res = tokio::task::spawn_blocking(move || {
-        binder::public_api::get_interface::<T>(&name)
+        binder::get_interface::<T>(&name)
     }).await;
 
     // The `is_panic` branch is not actually reachable in Android as we compile
@@ -61,12 +61,12 @@
 pub async fn wait_for_interface<T: FromIBinder + ?Sized + 'static>(name: &str) -> Result<Strong<T>, StatusCode> {
     if binder::is_handling_transaction() {
         // See comment in the BinderAsyncPool impl.
-        return binder::public_api::wait_for_interface::<T>(name);
+        return binder::wait_for_interface::<T>(name);
     }
 
     let name = name.to_string();
     let res = tokio::task::spawn_blocking(move || {
-        binder::public_api::wait_for_interface::<T>(&name)
+        binder::wait_for_interface::<T>(&name)
     }).await;
 
     // The `is_panic` branch is not actually reachable in Android as we compile
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index 4d6b294..467e51e 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -192,9 +192,6 @@
     /// Is this object still alive?
     fn is_binder_alive(&self) -> bool;
 
-    /// Send a ping transaction to this object
-    fn ping_binder(&mut self) -> Result<()>;
-
     /// Indicate that the service intends to receive caller security contexts.
     #[cfg(not(android_vndk))]
     fn set_requesting_sid(&mut self, enable: bool);
@@ -270,6 +267,9 @@
     /// The recipient will no longer be called if this object
     /// dies.
     fn unlink_to_death(&mut self, recipient: &mut DeathRecipient) -> Result<()>;
+
+    /// Send a ping transaction to this object
+    fn ping_binder(&mut self) -> Result<()>;
 }
 
 /// Opaque reference to the type of a Binder interface.
@@ -536,13 +536,13 @@
 /// ```
 macro_rules! binder_fn_get_class {
     ($class:ty) => {
-        binder_fn_get_class!($crate::InterfaceClass::new::<$class>());
+        binder_fn_get_class!($crate::binder_impl::InterfaceClass::new::<$class>());
     };
 
     ($constructor:expr) => {
-        fn get_class() -> $crate::InterfaceClass {
+        fn get_class() -> $crate::binder_impl::InterfaceClass {
             static CLASS_INIT: std::sync::Once = std::sync::Once::new();
-            static mut CLASS: Option<$crate::InterfaceClass> = None;
+            static mut CLASS: Option<$crate::binder_impl::InterfaceClass> = None;
 
             CLASS_INIT.call_once(|| unsafe {
                 // Safety: This assignment is guarded by the `CLASS_INIT` `Once`
@@ -772,7 +772,7 @@
                 native: $native($on_transact),
                 proxy: $proxy {},
                 $(async: $async_interface,)?
-                stability: $crate::Stability::default(),
+                stability: $crate::binder_impl::Stability::default(),
             }
         }
     };
@@ -811,7 +811,7 @@
                     $($fname: $fty = $finit),*
                 },
                 $(async: $async_interface,)?
-                stability: $crate::Stability::default(),
+                stability: $crate::binder_impl::Stability::default(),
             }
         }
     };
@@ -828,9 +828,9 @@
     } => {
         $crate::declare_binder_interface! {
             $interface[$descriptor] {
-                @doc[concat!("A binder [`Remotable`]($crate::Remotable) that holds an [`", stringify!($interface), "`] object.")]
+                @doc[concat!("A binder [`Remotable`]($crate::binder_impl::Remotable) that holds an [`", stringify!($interface), "`] object.")]
                 native: $native($on_transact),
-                @doc[concat!("A binder [`Proxy`]($crate::Proxy) that holds an [`", stringify!($interface), "`] remote interface.")]
+                @doc[concat!("A binder [`Proxy`]($crate::binder_impl::Proxy) that holds an [`", stringify!($interface), "`] remote interface.")]
                 proxy: $proxy {
                     $($fname: $fty = $finit),*
                 },
@@ -867,7 +867,7 @@
             }
         }
 
-        impl $crate::Proxy for $proxy
+        impl $crate::binder_impl::Proxy for $proxy
         where
             $proxy: $interface,
         {
@@ -875,7 +875,7 @@
                 $descriptor
             }
 
-            fn from_binder(mut binder: $crate::SpIBinder) -> $crate::Result<Self> {
+            fn from_binder(mut binder: $crate::SpIBinder) -> std::result::Result<Self, $crate::StatusCode> {
                 Ok(Self { binder, $($fname: $finit),* })
             }
         }
@@ -887,19 +887,19 @@
         impl $native {
             /// Create a new binder service.
             pub fn new_binder<T: $interface + Sync + Send + 'static>(inner: T, features: $crate::BinderFeatures) -> $crate::Strong<dyn $interface> {
-                let mut binder = $crate::Binder::new_with_stability($native(Box::new(inner)), $stability);
+                let mut binder = $crate::binder_impl::Binder::new_with_stability($native(Box::new(inner)), $stability);
                 #[cfg(not(android_vndk))]
-                $crate::IBinderInternal::set_requesting_sid(&mut binder, features.set_requesting_sid);
+                $crate::binder_impl::IBinderInternal::set_requesting_sid(&mut binder, features.set_requesting_sid);
                 $crate::Strong::new(Box::new(binder))
             }
         }
 
-        impl $crate::Remotable for $native {
+        impl $crate::binder_impl::Remotable for $native {
             fn get_descriptor() -> &'static str {
                 $descriptor
             }
 
-            fn on_transact(&self, code: $crate::TransactionCode, data: &$crate::BorrowedParcel<'_>, reply: &mut $crate::BorrowedParcel<'_>) -> $crate::Result<()> {
+            fn on_transact(&self, code: $crate::binder_impl::TransactionCode, data: &$crate::binder_impl::BorrowedParcel<'_>, reply: &mut $crate::binder_impl::BorrowedParcel<'_>) -> std::result::Result<(), $crate::StatusCode> {
                 match $on_transact(&*self.0, code, data, reply) {
                     // The C++ backend converts UNEXPECTED_NULL into an exception
                     Err($crate::StatusCode::UNEXPECTED_NULL) => {
@@ -913,19 +913,19 @@
                 }
             }
 
-            fn on_dump(&self, file: &std::fs::File, args: &[&std::ffi::CStr]) -> $crate::Result<()> {
+            fn on_dump(&self, file: &std::fs::File, args: &[&std::ffi::CStr]) -> std::result::Result<(), $crate::StatusCode> {
                 self.0.dump(file, args)
             }
 
-            fn get_class() -> $crate::InterfaceClass {
+            fn get_class() -> $crate::binder_impl::InterfaceClass {
                 static CLASS_INIT: std::sync::Once = std::sync::Once::new();
-                static mut CLASS: Option<$crate::InterfaceClass> = None;
+                static mut CLASS: Option<$crate::binder_impl::InterfaceClass> = None;
 
                 CLASS_INIT.call_once(|| unsafe {
                     // Safety: This assignment is guarded by the `CLASS_INIT` `Once`
                     // variable, and therefore is thread-safe, as it can only occur
                     // once.
-                    CLASS = Some($crate::InterfaceClass::new::<$crate::Binder<$native>>());
+                    CLASS = Some($crate::binder_impl::InterfaceClass::new::<$crate::binder_impl::Binder<$native>>());
                 });
                 unsafe {
                     // Safety: The `CLASS` variable can only be mutated once, above,
@@ -936,25 +936,25 @@
         }
 
         impl $crate::FromIBinder for dyn $interface {
-            fn try_from(mut ibinder: $crate::SpIBinder) -> $crate::Result<$crate::Strong<dyn $interface>> {
-                use $crate::AssociateClass;
+            fn try_from(mut ibinder: $crate::SpIBinder) -> std::result::Result<$crate::Strong<dyn $interface>, $crate::StatusCode> {
+                use $crate::binder_impl::AssociateClass;
 
                 let existing_class = ibinder.get_class();
                 if let Some(class) = existing_class {
-                    if class != <$native as $crate::Remotable>::get_class() &&
-                        class.get_descriptor() == <$native as $crate::Remotable>::get_descriptor()
+                    if class != <$native as $crate::binder_impl::Remotable>::get_class() &&
+                        class.get_descriptor() == <$native as $crate::binder_impl::Remotable>::get_descriptor()
                     {
                         // The binder object's descriptor string matches what we
                         // expect. We still need to treat this local or already
                         // associated object as remote, because we can't cast it
                         // into a Rust service object without a matching class
                         // pointer.
-                        return Ok($crate::Strong::new(Box::new(<$proxy as $crate::Proxy>::from_binder(ibinder)?)));
+                        return Ok($crate::Strong::new(Box::new(<$proxy as $crate::binder_impl::Proxy>::from_binder(ibinder)?)));
                     }
                 }
 
-                if ibinder.associate_class(<$native as $crate::Remotable>::get_class()) {
-                    let service: $crate::Result<$crate::Binder<$native>> =
+                if ibinder.associate_class(<$native as $crate::binder_impl::Remotable>::get_class()) {
+                    let service: std::result::Result<$crate::binder_impl::Binder<$native>, $crate::StatusCode> =
                         std::convert::TryFrom::try_from(ibinder.clone());
                     if let Ok(service) = service {
                         // We were able to associate with our expected class and
@@ -962,7 +962,7 @@
                         return Ok($crate::Strong::new(Box::new(service)));
                     } else {
                         // Service is remote
-                        return Ok($crate::Strong::new(Box::new(<$proxy as $crate::Proxy>::from_binder(ibinder)?)));
+                        return Ok($crate::Strong::new(Box::new(<$proxy as $crate::binder_impl::Proxy>::from_binder(ibinder)?)));
                     }
                 }
 
@@ -970,18 +970,18 @@
             }
         }
 
-        impl $crate::parcel::Serialize for dyn $interface + '_
+        impl $crate::binder_impl::Serialize for dyn $interface + '_
         where
             dyn $interface: $crate::Interface
         {
-            fn serialize(&self, parcel: &mut $crate::parcel::BorrowedParcel<'_>) -> $crate::Result<()> {
+            fn serialize(&self, parcel: &mut $crate::binder_impl::BorrowedParcel<'_>) -> std::result::Result<(), $crate::StatusCode> {
                 let binder = $crate::Interface::as_binder(self);
                 parcel.write(&binder)
             }
         }
 
-        impl $crate::parcel::SerializeOption for dyn $interface + '_ {
-            fn serialize_option(this: Option<&Self>, parcel: &mut $crate::parcel::BorrowedParcel<'_>) -> $crate::Result<()> {
+        impl $crate::binder_impl::SerializeOption for dyn $interface + '_ {
+            fn serialize_option(this: Option<&Self>, parcel: &mut $crate::binder_impl::BorrowedParcel<'_>) -> std::result::Result<(), $crate::StatusCode> {
                 parcel.write(&this.map($crate::Interface::as_binder))
             }
         }
@@ -1004,25 +1004,25 @@
         $(
         // Async interface trait implementations.
         impl<P: $crate::BinderAsyncPool> $crate::FromIBinder for dyn $async_interface<P> {
-            fn try_from(mut ibinder: $crate::SpIBinder) -> $crate::Result<$crate::Strong<dyn $async_interface<P>>> {
-                use $crate::AssociateClass;
+            fn try_from(mut ibinder: $crate::SpIBinder) -> std::result::Result<$crate::Strong<dyn $async_interface<P>>, $crate::StatusCode> {
+                use $crate::binder_impl::AssociateClass;
 
                 let existing_class = ibinder.get_class();
                 if let Some(class) = existing_class {
-                    if class != <$native as $crate::Remotable>::get_class() &&
-                        class.get_descriptor() == <$native as $crate::Remotable>::get_descriptor()
+                    if class != <$native as $crate::binder_impl::Remotable>::get_class() &&
+                        class.get_descriptor() == <$native as $crate::binder_impl::Remotable>::get_descriptor()
                     {
                         // The binder object's descriptor string matches what we
                         // expect. We still need to treat this local or already
                         // associated object as remote, because we can't cast it
                         // into a Rust service object without a matching class
                         // pointer.
-                        return Ok($crate::Strong::new(Box::new(<$proxy as $crate::Proxy>::from_binder(ibinder)?)));
+                        return Ok($crate::Strong::new(Box::new(<$proxy as $crate::binder_impl::Proxy>::from_binder(ibinder)?)));
                     }
                 }
 
-                if ibinder.associate_class(<$native as $crate::Remotable>::get_class()) {
-                    let service: $crate::Result<$crate::Binder<$native>> =
+                if ibinder.associate_class(<$native as $crate::binder_impl::Remotable>::get_class()) {
+                    let service: std::result::Result<$crate::binder_impl::Binder<$native>, $crate::StatusCode> =
                         std::convert::TryFrom::try_from(ibinder.clone());
                     if let Ok(service) = service {
                         // We were able to associate with our expected class and
@@ -1031,7 +1031,7 @@
                         //return Ok($crate::Strong::new(Box::new(service)));
                     } else {
                         // Service is remote
-                        return Ok($crate::Strong::new(Box::new(<$proxy as $crate::Proxy>::from_binder(ibinder)?)));
+                        return Ok($crate::Strong::new(Box::new(<$proxy as $crate::binder_impl::Proxy>::from_binder(ibinder)?)));
                     }
                 }
 
@@ -1039,15 +1039,15 @@
             }
         }
 
-        impl<P: $crate::BinderAsyncPool> $crate::parcel::Serialize for dyn $async_interface<P> + '_ {
-            fn serialize(&self, parcel: &mut $crate::parcel::BorrowedParcel<'_>) -> $crate::Result<()> {
+        impl<P: $crate::BinderAsyncPool> $crate::binder_impl::Serialize for dyn $async_interface<P> + '_ {
+            fn serialize(&self, parcel: &mut $crate::binder_impl::BorrowedParcel<'_>) -> std::result::Result<(), $crate::StatusCode> {
                 let binder = $crate::Interface::as_binder(self);
                 parcel.write(&binder)
             }
         }
 
-        impl<P: $crate::BinderAsyncPool> $crate::parcel::SerializeOption for dyn $async_interface<P> + '_ {
-            fn serialize_option(this: Option<&Self>, parcel: &mut $crate::parcel::BorrowedParcel<'_>) -> $crate::Result<()> {
+        impl<P: $crate::BinderAsyncPool> $crate::binder_impl::SerializeOption for dyn $async_interface<P> + '_ {
+            fn serialize_option(this: Option<&Self>, parcel: &mut $crate::binder_impl::BorrowedParcel<'_>) -> std::result::Result<(), $crate::StatusCode> {
                 parcel.write(&this.map($crate::Interface::as_binder))
             }
         }
@@ -1067,11 +1067,11 @@
             }
         }
 
-        impl<P: $crate::BinderAsyncPool> $crate::ToAsyncInterface<P> for dyn $interface {
+        impl<P: $crate::BinderAsyncPool> $crate::binder_impl::ToAsyncInterface<P> for dyn $interface {
             type Target = dyn $async_interface<P>;
         }
 
-        impl<P: $crate::BinderAsyncPool> $crate::ToSyncInterface for dyn $async_interface<P> {
+        impl<P: $crate::BinderAsyncPool> $crate::binder_impl::ToSyncInterface for dyn $async_interface<P> {
             type Target = dyn $interface;
         }
         )?
@@ -1103,29 +1103,29 @@
             }
         }
 
-        impl $crate::parcel::Serialize for $enum {
-            fn serialize(&self, parcel: &mut $crate::parcel::BorrowedParcel<'_>) -> $crate::Result<()> {
+        impl $crate::binder_impl::Serialize for $enum {
+            fn serialize(&self, parcel: &mut $crate::binder_impl::BorrowedParcel<'_>) -> std::result::Result<(), $crate::StatusCode> {
                 parcel.write(&self.0)
             }
         }
 
-        impl $crate::parcel::SerializeArray for $enum {
-            fn serialize_array(slice: &[Self], parcel: &mut $crate::parcel::BorrowedParcel<'_>) -> $crate::Result<()> {
+        impl $crate::binder_impl::SerializeArray for $enum {
+            fn serialize_array(slice: &[Self], parcel: &mut $crate::binder_impl::BorrowedParcel<'_>) -> std::result::Result<(), $crate::StatusCode> {
                 let v: Vec<$backing> = slice.iter().map(|x| x.0).collect();
-                <$backing as binder::parcel::SerializeArray>::serialize_array(&v[..], parcel)
+                <$backing as $crate::binder_impl::SerializeArray>::serialize_array(&v[..], parcel)
             }
         }
 
-        impl $crate::parcel::Deserialize for $enum {
-            fn deserialize(parcel: &$crate::parcel::BorrowedParcel<'_>) -> $crate::Result<Self> {
+        impl $crate::binder_impl::Deserialize for $enum {
+            fn deserialize(parcel: &$crate::binder_impl::BorrowedParcel<'_>) -> std::result::Result<Self, $crate::StatusCode> {
                 parcel.read().map(Self)
             }
         }
 
-        impl $crate::parcel::DeserializeArray for $enum {
-            fn deserialize_array(parcel: &$crate::parcel::BorrowedParcel<'_>) -> $crate::Result<Option<Vec<Self>>> {
+        impl $crate::binder_impl::DeserializeArray for $enum {
+            fn deserialize_array(parcel: &$crate::binder_impl::BorrowedParcel<'_>) -> std::result::Result<Option<Vec<Self>>, $crate::StatusCode> {
                 let v: Option<Vec<$backing>> =
-                    <$backing as binder::parcel::DeserializeArray>::deserialize_array(parcel)?;
+                    <$backing as $crate::binder_impl::DeserializeArray>::deserialize_array(parcel)?;
                 Ok(v.map(|v| v.into_iter().map(Self).collect()))
             }
         }
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index 20d90f7..1d7de98 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -101,45 +101,50 @@
 mod binder_async;
 mod error;
 mod native;
+mod parcel;
 mod state;
 
 use binder_ndk_sys as sys;
 
-pub mod parcel;
-
-pub use crate::binder::{
-    BinderFeatures, FromIBinder, IBinder, IBinderInternal, Interface, InterfaceClass, Remotable,
-    Stability, Strong, ToAsyncInterface, ToSyncInterface, TransactionCode, TransactionFlags, Weak,
-    FIRST_CALL_TRANSACTION, FLAG_CLEAR_BUF, FLAG_ONEWAY, FLAG_PRIVATE_LOCAL, LAST_CALL_TRANSACTION,
+pub use binder::{BinderFeatures, FromIBinder, IBinder, Interface, Strong, Weak};
+pub use crate::binder_async::{BinderAsyncPool, BoxFuture};
+pub use error::{ExceptionCode, Status, StatusCode};
+pub use native::{
+    add_service, force_lazy_services_persist, is_handling_transaction, register_lazy_service,
 };
-pub use crate::binder_async::{BoxFuture, BinderAsyncPool, BinderAsyncRuntime};
-pub use error::{status_t, ExceptionCode, Result, Status, StatusCode};
-pub use native::{add_service, force_lazy_services_persist, is_handling_transaction, register_lazy_service, Binder};
-pub use parcel::{BorrowedParcel, Parcel};
-pub use proxy::{get_interface, get_service, wait_for_interface, wait_for_service};
-pub use proxy::{AssociateClass, DeathRecipient, Proxy, SpIBinder, WpIBinder};
+pub use parcel::{ParcelFileDescriptor, Parcelable, ParcelableHolder};
+pub use proxy::{
+    get_interface, get_service, wait_for_interface, wait_for_service, DeathRecipient, SpIBinder,
+    WpIBinder,
+};
 pub use state::{ProcessState, ThreadState};
 
+/// Binder result containing a [`Status`] on error.
+pub type Result<T> = std::result::Result<T, Status>;
+
+/// Advanced Binder APIs needed internally by AIDL or when manually using Binder
+/// without AIDL.
+pub mod binder_impl {
+    pub use crate::binder::{
+        IBinderInternal, InterfaceClass, Remotable, Stability, ToAsyncInterface, ToSyncInterface,
+        TransactionCode, TransactionFlags, FIRST_CALL_TRANSACTION, FLAG_CLEAR_BUF, FLAG_ONEWAY,
+        FLAG_PRIVATE_LOCAL, LAST_CALL_TRANSACTION,
+    };
+    pub use crate::binder_async::BinderAsyncRuntime;
+    pub use crate::error::status_t;
+    pub use crate::native::Binder;
+    pub use crate::parcel::{
+        BorrowedParcel, Deserialize, DeserializeArray, DeserializeOption, Parcel,
+        ParcelableMetadata, Serialize, SerializeArray, SerializeOption, NON_NULL_PARCELABLE_FLAG,
+        NULL_PARCELABLE_FLAG,
+    };
+    pub use crate::proxy::{AssociateClass, Proxy};
+}
+
 /// Unstable, in-development API that only allowlisted clients are allowed to use.
+#[doc(hidden)]
 pub mod unstable_api {
     pub use crate::binder::AsNative;
     pub use crate::proxy::unstable_api::new_spibinder;
     pub use crate::sys::AIBinder;
 }
-
-/// The public API usable outside AIDL-generated interface crates.
-pub mod public_api {
-    pub use super::parcel::{ParcelFileDescriptor, ParcelableHolder};
-    pub use super::{
-        add_service, force_lazy_services_persist, get_interface, register_lazy_service,
-        wait_for_interface,
-    };
-    pub use super::{
-        BinderAsyncPool, BinderFeatures, BoxFuture, DeathRecipient, ExceptionCode, IBinder,
-        Interface, ProcessState, SpIBinder, Status, StatusCode, Strong, ThreadState, Weak,
-        WpIBinder,
-    };
-
-    /// Binder result containing a [`Status`] on error.
-    pub type Result<T> = std::result::Result<T, Status>;
-}
diff --git a/libs/binder/rust/src/parcel/parcelable.rs b/libs/binder/rust/src/parcel/parcelable.rs
index 61f88b6..0c7e48d 100644
--- a/libs/binder/rust/src/parcel/parcelable.rs
+++ b/libs/binder/rust/src/parcel/parcelable.rs
@@ -802,35 +802,32 @@
 #[macro_export]
 macro_rules! impl_serialize_for_parcelable {
     ($parcelable:ident) => {
-        impl $crate::parcel::Serialize for $parcelable {
+        impl $crate::binder_impl::Serialize for $parcelable {
             fn serialize(
                 &self,
-                parcel: &mut $crate::parcel::BorrowedParcel<'_>,
-            ) -> $crate::Result<()> {
-                <Self as $crate::parcel::SerializeOption>::serialize_option(
-                    Some(self),
-                    parcel,
-                )
+                parcel: &mut $crate::binder_impl::BorrowedParcel<'_>,
+            ) -> std::result::Result<(), $crate::StatusCode> {
+                <Self as $crate::binder_impl::SerializeOption>::serialize_option(Some(self), parcel)
             }
         }
 
-        impl $crate::parcel::SerializeArray for $parcelable {}
+        impl $crate::binder_impl::SerializeArray for $parcelable {}
 
-        impl $crate::parcel::SerializeOption for $parcelable {
+        impl $crate::binder_impl::SerializeOption for $parcelable {
             fn serialize_option(
                 this: Option<&Self>,
-                parcel: &mut $crate::parcel::BorrowedParcel<'_>,
-            ) -> $crate::Result<()> {
+                parcel: &mut $crate::binder_impl::BorrowedParcel<'_>,
+            ) -> std::result::Result<(), $crate::StatusCode> {
                 if let Some(this) = this {
-                    use $crate::parcel::Parcelable;
-                    parcel.write(&$crate::parcel::NON_NULL_PARCELABLE_FLAG)?;
+                    use $crate::Parcelable;
+                    parcel.write(&$crate::binder_impl::NON_NULL_PARCELABLE_FLAG)?;
                     this.write_to_parcel(parcel)
                 } else {
-                    parcel.write(&$crate::parcel::NULL_PARCELABLE_FLAG)
+                    parcel.write(&$crate::binder_impl::NULL_PARCELABLE_FLAG)
                 }
             }
         }
-    }
+    };
 }
 
 /// Implement `Deserialize` trait and friends for a parcelable
@@ -842,54 +839,54 @@
 #[macro_export]
 macro_rules! impl_deserialize_for_parcelable {
     ($parcelable:ident) => {
-        impl $crate::parcel::Deserialize for $parcelable {
+        impl $crate::binder_impl::Deserialize for $parcelable {
             fn deserialize(
-                parcel: &$crate::parcel::BorrowedParcel<'_>,
-            ) -> $crate::Result<Self> {
-                $crate::parcel::DeserializeOption::deserialize_option(parcel)
+                parcel: &$crate::binder_impl::BorrowedParcel<'_>,
+            ) -> std::result::Result<Self, $crate::StatusCode> {
+                $crate::binder_impl::DeserializeOption::deserialize_option(parcel)
                     .transpose()
                     .unwrap_or(Err($crate::StatusCode::UNEXPECTED_NULL))
             }
             fn deserialize_from(
                 &mut self,
-                parcel: &$crate::parcel::BorrowedParcel<'_>,
-            ) -> $crate::Result<()> {
+                parcel: &$crate::binder_impl::BorrowedParcel<'_>,
+            ) -> std::result::Result<(), $crate::StatusCode> {
                 let status: i32 = parcel.read()?;
-                if status == $crate::parcel::NULL_PARCELABLE_FLAG {
+                if status == $crate::binder_impl::NULL_PARCELABLE_FLAG {
                     Err($crate::StatusCode::UNEXPECTED_NULL)
                 } else {
-                    use $crate::parcel::Parcelable;
+                    use $crate::Parcelable;
                     self.read_from_parcel(parcel)
                 }
             }
         }
 
-        impl $crate::parcel::DeserializeArray for $parcelable {}
+        impl $crate::binder_impl::DeserializeArray for $parcelable {}
 
-        impl $crate::parcel::DeserializeOption for $parcelable {
+        impl $crate::binder_impl::DeserializeOption for $parcelable {
             fn deserialize_option(
-                parcel: &$crate::parcel::BorrowedParcel<'_>,
-            ) -> $crate::Result<Option<Self>> {
+                parcel: &$crate::binder_impl::BorrowedParcel<'_>,
+            ) -> std::result::Result<Option<Self>, $crate::StatusCode> {
                 let mut result = None;
                 Self::deserialize_option_from(&mut result, parcel)?;
                 Ok(result)
             }
             fn deserialize_option_from(
                 this: &mut Option<Self>,
-                parcel: &$crate::parcel::BorrowedParcel<'_>,
-            ) -> $crate::Result<()> {
+                parcel: &$crate::binder_impl::BorrowedParcel<'_>,
+            ) -> std::result::Result<(), $crate::StatusCode> {
                 let status: i32 = parcel.read()?;
-                if status == $crate::parcel::NULL_PARCELABLE_FLAG {
+                if status == $crate::binder_impl::NULL_PARCELABLE_FLAG {
                     *this = None;
                     Ok(())
                 } else {
-                    use $crate::parcel::Parcelable;
+                    use $crate::Parcelable;
                     this.get_or_insert_with(Self::default)
                         .read_from_parcel(parcel)
                 }
             }
         }
-    }
+    };
 }
 
 impl<T: Serialize> Serialize for Box<T> {
@@ -918,7 +915,7 @@
 
 #[cfg(test)]
 mod tests {
-    use crate::Parcel;
+    use crate::parcel::Parcel;
     use super::*;
 
     #[test]
diff --git a/libs/binder/rust/src/parcel/parcelable_holder.rs b/libs/binder/rust/src/parcel/parcelable_holder.rs
index b4282b2..d58e839 100644
--- a/libs/binder/rust/src/parcel/parcelable_holder.rs
+++ b/libs/binder/rust/src/parcel/parcelable_holder.rs
@@ -15,9 +15,11 @@
  */
 
 use crate::binder::Stability;
-use crate::error::{Result, StatusCode};
-use crate::parcel::{Parcel, BorrowedParcel, Parcelable};
-use crate::{impl_deserialize_for_parcelable, impl_serialize_for_parcelable};
+use crate::error::StatusCode;
+use crate::parcel::{
+    BorrowedParcel, Deserialize, Parcel, Parcelable, Serialize, NON_NULL_PARCELABLE_FLAG,
+    NULL_PARCELABLE_FLAG,
+};
 
 use downcast_rs::{impl_downcast, DowncastSync};
 use std::any::Any;
@@ -53,12 +55,6 @@
     Parcel(Parcel),
 }
 
-impl Default for ParcelableHolderData {
-    fn default() -> Self {
-        ParcelableHolderData::Empty
-    }
-}
-
 /// A container that can hold any arbitrary `Parcelable`.
 ///
 /// This type is currently used for AIDL parcelable fields.
@@ -66,7 +62,7 @@
 /// `ParcelableHolder` is currently not thread-safe (neither
 /// `Send` nor `Sync`), mainly because it internally contains
 /// a `Parcel` which in turn is not thread-safe.
-#[derive(Debug, Default)]
+#[derive(Debug)]
 pub struct ParcelableHolder {
     // This is a `Mutex` because of `get_parcelable`
     // which takes `&self` for consistency with C++.
@@ -97,7 +93,7 @@
     }
 
     /// Set the parcelable contained in this `ParcelableHolder`.
-    pub fn set_parcelable<T>(&mut self, p: Arc<T>) -> Result<()>
+    pub fn set_parcelable<T>(&mut self, p: Arc<T>) -> Result<(), StatusCode>
     where
         T: Any + Parcelable + ParcelableMetadata + std::fmt::Debug + Send + Sync,
     {
@@ -126,7 +122,7 @@
     /// * `Ok(None)` if the holder is empty or the descriptor does not match
     /// * `Ok(Some(_))` if the object holds a parcelable of type `T`
     ///   with the correct descriptor
-    pub fn get_parcelable<T>(&self) -> Result<Option<Arc<T>>>
+    pub fn get_parcelable<T>(&self) -> Result<Option<Arc<T>>, StatusCode>
     where
         T: Any + Parcelable + ParcelableMetadata + Default + std::fmt::Debug + Send + Sync,
     {
@@ -176,11 +172,28 @@
     }
 }
 
-impl_serialize_for_parcelable!(ParcelableHolder);
-impl_deserialize_for_parcelable!(ParcelableHolder);
+impl Serialize for ParcelableHolder {
+    fn serialize(&self, parcel: &mut BorrowedParcel<'_>) -> Result<(), StatusCode> {
+        parcel.write(&NON_NULL_PARCELABLE_FLAG)?;
+        self.write_to_parcel(parcel)
+    }
+}
+
+impl Deserialize for ParcelableHolder {
+    fn deserialize(parcel: &BorrowedParcel<'_>) -> Result<Self, StatusCode> {
+        let status: i32 = parcel.read()?;
+        if status == NULL_PARCELABLE_FLAG {
+            Err(StatusCode::UNEXPECTED_NULL)
+        } else {
+            let mut parcelable = ParcelableHolder::new(Default::default());
+            parcelable.read_from_parcel(parcel)?;
+            Ok(parcelable)
+        }
+    }
+}
 
 impl Parcelable for ParcelableHolder {
-    fn write_to_parcel(&self, parcel: &mut BorrowedParcel<'_>) -> Result<()> {
+    fn write_to_parcel(&self, parcel: &mut BorrowedParcel<'_>) -> Result<(), StatusCode> {
         parcel.write(&self.stability)?;
 
         let mut data = self.data.lock().unwrap();
@@ -219,7 +232,7 @@
         }
     }
 
-    fn read_from_parcel(&mut self, parcel: &BorrowedParcel<'_>) -> Result<()> {
+    fn read_from_parcel(&mut self, parcel: &BorrowedParcel<'_>) -> Result<(), StatusCode> {
         self.stability = parcel.read()?;
 
         let data_size: i32 = parcel.read()?;
diff --git a/libs/binder/rust/src/proxy.rs b/libs/binder/rust/src/proxy.rs
index 760d862..12bfde7 100644
--- a/libs/binder/rust/src/proxy.rs
+++ b/libs/binder/rust/src/proxy.rs
@@ -312,17 +312,6 @@
         }
     }
 
-    fn ping_binder(&mut self) -> Result<()> {
-        let status = unsafe {
-            // Safety: `SpIBinder` guarantees that `self` always contains a
-            // valid pointer to an `AIBinder`.
-            //
-            // This call does not affect ownership of its pointer parameter.
-            sys::AIBinder_ping(self.as_native_mut())
-        };
-        status_result(status)
-    }
-
     #[cfg(not(android_vndk))]
     fn set_requesting_sid(&mut self, enable: bool) {
         unsafe { sys::AIBinder_setRequestingSid(self.as_native_mut(), enable) };
@@ -412,6 +401,17 @@
             )
         })
     }
+
+    fn ping_binder(&mut self) -> Result<()> {
+        let status = unsafe {
+            // Safety: `SpIBinder` guarantees that `self` always contains a
+            // valid pointer to an `AIBinder`.
+            //
+            // This call does not affect ownership of its pointer parameter.
+            sys::AIBinder_ping(self.as_native_mut())
+        };
+        status_result(status)
+    }
 }
 
 impl Serialize for SpIBinder {
diff --git a/libs/binder/rust/tests/integration.rs b/libs/binder/rust/tests/integration.rs
index 80dc476..50daf1c 100644
--- a/libs/binder/rust/tests/integration.rs
+++ b/libs/binder/rust/tests/integration.rs
@@ -17,11 +17,13 @@
 //! Rust Binder crate integration tests
 
 use binder::{declare_binder_enum, declare_binder_interface};
-use binder::parcel::BorrowedParcel;
-use binder::{
-    Binder, BinderFeatures, IBinderInternal, Interface, StatusCode, ThreadState, TransactionCode,
-    FIRST_CALL_TRANSACTION,
+use binder::{BinderFeatures, Interface, StatusCode, ThreadState};
+// Import from internal API for testing only, do not use this module in
+// production.
+use binder::binder_impl::{
+    Binder, BorrowedParcel, IBinderInternal, TransactionCode, FIRST_CALL_TRANSACTION,
 };
+
 use std::convert::{TryFrom, TryInto};
 use std::ffi::CStr;
 use std::fs::File;
@@ -120,7 +122,7 @@
 }
 
 impl Interface for TestService {
-    fn dump(&self, _file: &File, args: &[&CStr]) -> binder::Result<()> {
+    fn dump(&self, _file: &File, args: &[&CStr]) -> Result<(), StatusCode> {
         let mut dump_args = self.dump_args.lock().unwrap();
         dump_args.extend(args.iter().map(|s| s.to_str().unwrap().to_owned()));
         Ok(())
@@ -128,22 +130,22 @@
 }
 
 impl ITest for TestService {
-    fn test(&self) -> binder::Result<String> {
+    fn test(&self) -> Result<String, StatusCode> {
         Ok(self.s.clone())
     }
 
-    fn get_dump_args(&self) -> binder::Result<Vec<String>> {
+    fn get_dump_args(&self) -> Result<Vec<String>, StatusCode> {
         let args = self.dump_args.lock().unwrap().clone();
         Ok(args)
     }
 
-    fn get_selinux_context(&self) -> binder::Result<String> {
+    fn get_selinux_context(&self) -> Result<String, StatusCode> {
         let sid =
             ThreadState::with_calling_sid(|sid| sid.map(|s| s.to_string_lossy().into_owned()));
         sid.ok_or(StatusCode::UNEXPECTED_NULL)
     }
 
-    fn get_is_handling_transaction(&self) -> binder::Result<bool> {
+    fn get_is_handling_transaction(&self) -> Result<bool, StatusCode> {
         Ok(binder::is_handling_transaction())
     }
 }
@@ -151,31 +153,31 @@
 /// Trivial testing binder interface
 pub trait ITest: Interface {
     /// Returns a test string
-    fn test(&self) -> binder::Result<String>;
+    fn test(&self) -> Result<String, StatusCode>;
 
     /// Return the arguments sent via dump
-    fn get_dump_args(&self) -> binder::Result<Vec<String>>;
+    fn get_dump_args(&self) -> Result<Vec<String>, StatusCode>;
 
     /// Returns the caller's SELinux context
-    fn get_selinux_context(&self) -> binder::Result<String>;
+    fn get_selinux_context(&self) -> Result<String, StatusCode>;
 
     /// Returns the value of calling `is_handling_transaction`.
-    fn get_is_handling_transaction(&self) -> binder::Result<bool>;
+    fn get_is_handling_transaction(&self) -> Result<bool, StatusCode>;
 }
 
 /// Async trivial testing binder interface
 pub trait IATest<P>: Interface {
     /// Returns a test string
-    fn test(&self) -> binder::BoxFuture<'static, binder::Result<String>>;
+    fn test(&self) -> binder::BoxFuture<'static, Result<String, StatusCode>>;
 
     /// Return the arguments sent via dump
-    fn get_dump_args(&self) -> binder::BoxFuture<'static, binder::Result<Vec<String>>>;
+    fn get_dump_args(&self) -> binder::BoxFuture<'static, Result<Vec<String>, StatusCode>>;
 
     /// Returns the caller's SELinux context
-    fn get_selinux_context(&self) -> binder::BoxFuture<'static, binder::Result<String>>;
+    fn get_selinux_context(&self) -> binder::BoxFuture<'static, Result<String, StatusCode>>;
 
     /// Returns the value of calling `is_handling_transaction`.
-    fn get_is_handling_transaction(&self) -> binder::BoxFuture<'static, binder::Result<bool>>;
+    fn get_is_handling_transaction(&self) -> binder::BoxFuture<'static, Result<bool, StatusCode>>;
 }
 
 declare_binder_interface! {
@@ -193,7 +195,7 @@
     code: TransactionCode,
     _data: &BorrowedParcel<'_>,
     reply: &mut BorrowedParcel<'_>,
-) -> binder::Result<()> {
+) -> Result<(), StatusCode> {
     match code.try_into()? {
         TestTransactionCode::Test => reply.write(&service.test()?),
         TestTransactionCode::GetDumpArgs => reply.write(&service.get_dump_args()?),
@@ -203,21 +205,21 @@
 }
 
 impl ITest for BpTest {
-    fn test(&self) -> binder::Result<String> {
+    fn test(&self) -> Result<String, StatusCode> {
         let reply =
             self.binder
                 .transact(TestTransactionCode::Test as TransactionCode, 0, |_| Ok(()))?;
         reply.read()
     }
 
-    fn get_dump_args(&self) -> binder::Result<Vec<String>> {
+    fn get_dump_args(&self) -> Result<Vec<String>, StatusCode> {
         let reply =
             self.binder
                 .transact(TestTransactionCode::GetDumpArgs as TransactionCode, 0, |_| Ok(()))?;
         reply.read()
     }
 
-    fn get_selinux_context(&self) -> binder::Result<String> {
+    fn get_selinux_context(&self) -> Result<String, StatusCode> {
         let reply = self.binder.transact(
             TestTransactionCode::GetSelinuxContext as TransactionCode,
             0,
@@ -226,7 +228,7 @@
         reply.read()
     }
 
-    fn get_is_handling_transaction(&self) -> binder::Result<bool> {
+    fn get_is_handling_transaction(&self) -> Result<bool, StatusCode> {
         let reply = self.binder.transact(
             TestTransactionCode::GetIsHandlingTransaction as TransactionCode,
             0,
@@ -237,7 +239,7 @@
 }
 
 impl<P: binder::BinderAsyncPool> IATest<P> for BpTest {
-    fn test(&self) -> binder::BoxFuture<'static, binder::Result<String>> {
+    fn test(&self) -> binder::BoxFuture<'static, Result<String, StatusCode>> {
         let binder = self.binder.clone();
         P::spawn(
             move || binder.transact(TestTransactionCode::Test as TransactionCode, 0, |_| Ok(())),
@@ -245,7 +247,7 @@
         )
     }
 
-    fn get_dump_args(&self) -> binder::BoxFuture<'static, binder::Result<Vec<String>>> {
+    fn get_dump_args(&self) -> binder::BoxFuture<'static, Result<Vec<String>, StatusCode>> {
         let binder = self.binder.clone();
         P::spawn(
             move || binder.transact(TestTransactionCode::GetDumpArgs as TransactionCode, 0, |_| Ok(())),
@@ -253,7 +255,7 @@
         )
     }
 
-    fn get_selinux_context(&self) -> binder::BoxFuture<'static, binder::Result<String>> {
+    fn get_selinux_context(&self) -> binder::BoxFuture<'static, Result<String, StatusCode>> {
         let binder = self.binder.clone();
         P::spawn(
             move || binder.transact(TestTransactionCode::GetSelinuxContext as TransactionCode, 0, |_| Ok(())),
@@ -261,7 +263,7 @@
         )
     }
 
-    fn get_is_handling_transaction(&self) -> binder::BoxFuture<'static, binder::Result<bool>> {
+    fn get_is_handling_transaction(&self) -> binder::BoxFuture<'static, Result<bool, StatusCode>> {
         let binder = self.binder.clone();
         P::spawn(
             move || binder.transact(TestTransactionCode::GetIsHandlingTransaction as TransactionCode, 0, |_| Ok(())),
@@ -271,40 +273,40 @@
 }
 
 impl ITest for Binder<BnTest> {
-    fn test(&self) -> binder::Result<String> {
+    fn test(&self) -> Result<String, StatusCode> {
         self.0.test()
     }
 
-    fn get_dump_args(&self) -> binder::Result<Vec<String>> {
+    fn get_dump_args(&self) -> Result<Vec<String>, StatusCode> {
         self.0.get_dump_args()
     }
 
-    fn get_selinux_context(&self) -> binder::Result<String> {
+    fn get_selinux_context(&self) -> Result<String, StatusCode> {
         self.0.get_selinux_context()
     }
 
-    fn get_is_handling_transaction(&self) -> binder::Result<bool> {
+    fn get_is_handling_transaction(&self) -> Result<bool, StatusCode> {
         self.0.get_is_handling_transaction()
     }
 }
 
 impl<P: binder::BinderAsyncPool> IATest<P> for Binder<BnTest> {
-    fn test(&self) -> binder::BoxFuture<'static, binder::Result<String>> {
+    fn test(&self) -> binder::BoxFuture<'static, Result<String, StatusCode>> {
         let res = self.0.test();
         Box::pin(async move { res })
     }
 
-    fn get_dump_args(&self) -> binder::BoxFuture<'static, binder::Result<Vec<String>>> {
+    fn get_dump_args(&self) -> binder::BoxFuture<'static, Result<Vec<String>, StatusCode>> {
         let res = self.0.get_dump_args();
         Box::pin(async move { res })
     }
 
-    fn get_selinux_context(&self) -> binder::BoxFuture<'static, binder::Result<String>> {
+    fn get_selinux_context(&self) -> binder::BoxFuture<'static, Result<String, StatusCode>> {
         let res = self.0.get_selinux_context();
         Box::pin(async move { res })
     }
 
-    fn get_is_handling_transaction(&self) -> binder::BoxFuture<'static, binder::Result<bool>> {
+    fn get_is_handling_transaction(&self) -> binder::BoxFuture<'static, Result<bool, StatusCode>> {
         let res = self.0.get_is_handling_transaction();
         Box::pin(async move { res })
     }
@@ -325,7 +327,7 @@
     _code: TransactionCode,
     _data: &BorrowedParcel<'_>,
     _reply: &mut BorrowedParcel<'_>,
-) -> binder::Result<()> {
+) -> Result<(), StatusCode> {
     Ok(())
 }
 
@@ -363,9 +365,12 @@
     use std::time::Duration;
 
     use binder::{
-        Binder, BinderFeatures, DeathRecipient, FromIBinder, IBinder, IBinderInternal, Interface,
-        SpIBinder, StatusCode, Strong,
+        BinderFeatures, DeathRecipient, FromIBinder, IBinder, Interface, SpIBinder, StatusCode,
+        Strong,
     };
+    // Import from impl API for testing only, should not be necessary as long as
+    // you are using AIDL.
+    use binder::binder_impl::{Binder, IBinderInternal, TransactionCode};
 
     use binder_tokio::Tokio;
 
@@ -743,8 +748,7 @@
             let _process = ScopedServiceProcess::new(service_name);
 
             let test_client: Strong<dyn ITest> =
-                binder::get_interface(service_name)
-                .expect("Did not get test binder service");
+                binder::get_interface(service_name).expect("Did not get test binder service");
             let mut remote = test_client.as_binder();
             assert!(remote.is_binder_alive());
             remote.ping_binder().expect("Could not ping remote service");
@@ -925,7 +929,7 @@
         let service2 = service2.as_binder();
 
         let parcel = service1.prepare_transact().unwrap();
-        let res = service2.submit_transact(super::TestTransactionCode::Test as binder::TransactionCode, parcel, 0);
+        let res = service2.submit_transact(super::TestTransactionCode::Test as TransactionCode, parcel, 0);
 
         match res {
             Ok(_) => panic!("submit_transact should fail"),
diff --git a/libs/binder/rust/tests/serialization.rs b/libs/binder/rust/tests/serialization.rs
index 1fc761e..b62da7b 100644
--- a/libs/binder/rust/tests/serialization.rs
+++ b/libs/binder/rust/tests/serialization.rs
@@ -18,11 +18,12 @@
 //! access.
 
 use binder::declare_binder_interface;
-use binder::parcel::ParcelFileDescriptor;
 use binder::{
-    Binder, BinderFeatures, BorrowedParcel, ExceptionCode, Interface, Result, SpIBinder, Status,
-    StatusCode, TransactionCode,
+    BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor, SpIBinder, Status, StatusCode,
 };
+// Import from impl API for testing only, should not be necessary as long as you
+// are using AIDL.
+use binder::binder_impl::{BorrowedParcel, Binder, TransactionCode};
 
 use std::ffi::{c_void, CStr, CString};
 use std::sync::Once;
@@ -113,7 +114,7 @@
     code: TransactionCode,
     parcel: &BorrowedParcel<'_>,
     reply: &mut BorrowedParcel<'_>,
-) -> Result<()> {
+) -> Result<(), StatusCode> {
     match code {
         bindings::Transaction_TEST_BOOL => {
             assert_eq!(parcel.read::<bool>()?, true);
diff --git a/libs/binder/tests/rpc_fuzzer/corpus/transact_on_binder b/libs/binder/tests/rpc_fuzzer/corpus/transact_on_binder
new file mode 100644
index 0000000..ae081e6
--- /dev/null
+++ b/libs/binder/tests/rpc_fuzzer/corpus/transact_on_binder
Binary files differ
diff --git a/libs/binderthreadstate/Android.bp b/libs/binderthreadstate/Android.bp
index 0a82463..4860613 100644
--- a/libs/binderthreadstate/Android.bp
+++ b/libs/binderthreadstate/Android.bp
@@ -31,7 +31,7 @@
     target: {
         darwin: {
             enabled: false,
-        }
+        },
     },
 
     shared_libs: [
@@ -46,6 +46,11 @@
         "-Werror",
     ],
     min_sdk_version: "29",
+    // static link, so it won't straddle a module boundary at runtime.
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media.swcodec",
+    ],
 }
 
 hidl_package_root {
diff --git a/libs/cputimeinstate/Android.bp b/libs/cputimeinstate/Android.bp
index 1fd2c62..73f9d4d 100644
--- a/libs/cputimeinstate/Android.bp
+++ b/libs/cputimeinstate/Android.bp
@@ -14,6 +14,7 @@
         "libbase",
         "libbpf_bcc",
         "libbpf_android",
+        "libbpf_minimal",
         "liblog",
         "libnetdutils"
     ],
@@ -33,6 +34,7 @@
         "libbase",
         "libbpf_bcc",
         "libbpf_android",
+        "libbpf_minimal",
         "libtimeinstate",
         "libnetdutils",
     ],
diff --git a/libs/ftl/enum_test.cpp b/libs/ftl/enum_test.cpp
index 1fd43ab..d8ce7a5 100644
--- a/libs/ftl/enum_test.cpp
+++ b/libs/ftl/enum_test.cpp
@@ -72,7 +72,7 @@
   kNeptune
 };
 
-constexpr Planet kPluto{ftl::enum_cast(Planet::kNeptune) + 1};  // Honorable mention.
+constexpr Planet kPluto{ftl::to_underlying(Planet::kNeptune) + 1};  // Honorable mention.
 
 static_assert(ftl::enum_begin_v<Planet> == Planet::kMercury);
 static_assert(ftl::enum_last_v<Planet> == Planet::kNeptune);
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 27d86bb..eec4a87 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -340,6 +340,79 @@
     }
 }
 
+void layer_state_t::sanitize(int32_t permissions) {
+    // TODO: b/109894387
+    //
+    // SurfaceFlinger's renderer is not prepared to handle cropping in the face of arbitrary
+    // rotation. To see the problem observe that if we have a square parent, and a child
+    // of the same size, then we rotate the child 45 degrees around its center, the child
+    // must now be cropped to a non rectangular 8 sided region.
+    //
+    // Of course we can fix this in the future. For now, we are lucky, SurfaceControl is
+    // private API, and arbitrary rotation is used in limited use cases, for instance:
+    // - WindowManager only uses rotation in one case, which is on a top level layer in which
+    //   cropping is not an issue.
+    // - Launcher, as a privileged app, uses this to transition an application to PiP
+    //   (picture-in-picture) mode.
+    //
+    // However given that abuse of rotation matrices could lead to surfaces extending outside
+    // of cropped areas, we need to prevent non-root clients without permission
+    // ACCESS_SURFACE_FLINGER nor ROTATE_SURFACE_FLINGER
+    // (a.k.a. everyone except WindowManager / tests / Launcher) from setting non rectangle
+    // preserving transformations.
+    if (what & eMatrixChanged) {
+        if (!(permissions & Permission::ROTATE_SURFACE_FLINGER)) {
+            ui::Transform t;
+            t.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy);
+            if (!t.preserveRects()) {
+                what &= ~eMatrixChanged;
+                ALOGE("Stripped non rect preserving matrix in sanitize");
+            }
+        }
+    }
+
+    if (what & eFlagsChanged) {
+        if ((flags & eLayerIsDisplayDecoration) &&
+            !(permissions & Permission::INTERNAL_SYSTEM_WINDOW)) {
+            flags &= ~eLayerIsDisplayDecoration;
+            ALOGE("Stripped attempt to set LayerIsDisplayDecoration in sanitize");
+        }
+    }
+
+    if (what & layer_state_t::eInputInfoChanged) {
+        if (!(permissions & Permission::ACCESS_SURFACE_FLINGER)) {
+            what &= ~eInputInfoChanged;
+            ALOGE("Stripped attempt to set eInputInfoChanged in sanitize");
+        }
+    }
+    if (what & layer_state_t::eTrustedOverlayChanged) {
+        if (!(permissions & Permission::ACCESS_SURFACE_FLINGER)) {
+            what &= ~eTrustedOverlayChanged;
+            ALOGE("Stripped attempt to set eTrustedOverlay in sanitize");
+        }
+    }
+    if (what & layer_state_t::eDropInputModeChanged) {
+        if (!(permissions & Permission::ACCESS_SURFACE_FLINGER)) {
+            what &= ~eDropInputModeChanged;
+            ALOGE("Stripped attempt to set eDropInputModeChanged in sanitize");
+        }
+    }
+    if (what & layer_state_t::eFrameRateSelectionPriority) {
+        if (!(permissions & Permission::ACCESS_SURFACE_FLINGER)) {
+            what &= ~eFrameRateSelectionPriority;
+            ALOGE("Stripped attempt to set eFrameRateSelectionPriority in sanitize");
+        }
+    }
+    if (what & layer_state_t::eFrameRateChanged) {
+        if (!ValidateFrameRate(frameRate, frameRateCompatibility,
+                               changeFrameRateStrategy,
+                               "layer_state_t::sanitize",
+                               permissions & Permission::ACCESS_SURFACE_FLINGER)) {
+            what &= ~eFrameRateChanged; // logged in ValidateFrameRate
+        }
+    }
+}
+
 void layer_state_t::merge(const layer_state_t& other) {
     if (other.what & ePositionChanged) {
         what |= ePositionChanged;
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 1bb5374..91b2fb1 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -565,6 +565,13 @@
     mListenerCallbacks = other.mListenerCallbacks;
 }
 
+void SurfaceComposerClient::Transaction::sanitize() {
+    for (auto & [handle, composerState] : mComposerStates) {
+        composerState.state.sanitize(0 /* permissionMask */);
+    }
+    mInputWindowCommands.clear();
+}
+
 std::unique_ptr<SurfaceComposerClient::Transaction>
 SurfaceComposerClient::Transaction::createFromParcel(const Parcel* parcel) {
     auto transaction = std::make_unique<Transaction>();
@@ -646,7 +653,6 @@
         if (composerState.read(*parcel) == BAD_VALUE) {
             return BAD_VALUE;
         }
-
         composerStates[surfaceControlHandle] = composerState;
     }
 
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index 8d356aa..1c7b270 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -52,7 +52,8 @@
 }
 
 bool WindowInfo::overlaps(const WindowInfo* other) const {
-    return frameLeft < other->frameRight && frameRight > other->frameLeft &&
+    const bool nonEmpty = (frameRight - frameLeft > 0) || (frameBottom - frameTop > 0);
+    return nonEmpty && frameLeft < other->frameRight && frameRight > other->frameLeft &&
             frameTop < other->frameBottom && frameBottom > other->frameTop;
 }
 
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 968ace9..cd6afd2 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -113,6 +113,12 @@
  * Used to communicate layer information between SurfaceFlinger and its clients.
  */
 struct layer_state_t {
+    enum Permission {
+        ACCESS_SURFACE_FLINGER = 0x1,
+        ROTATE_SURFACE_FLINGER = 0x2,
+        INTERNAL_SYSTEM_WINDOW = 0x4,
+    };
+
     enum {
         eLayerHidden = 0x01,         // SURFACE_HIDDEN in SurfaceControl.java
         eLayerOpaque = 0x02,         // SURFACE_OPAQUE
@@ -136,7 +142,7 @@
         eLayerStackChanged = 0x00000080,
         /* unused 0x00000400, */
         eShadowRadiusChanged = 0x00000800,
-        eLayerCreated = 0x00001000,
+        /* unused 0x00001000, */
         eBufferCropChanged = 0x00002000,
         eRelativeLayerChanged = 0x00004000,
         eReparent = 0x00008000,
@@ -181,6 +187,7 @@
     status_t read(const Parcel& input);
     bool hasBufferChanges() const;
     bool hasValidBuffer() const;
+    void sanitize(int32_t permissions);
 
     struct matrix22_t {
         float dsdx{0};
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index e9c7533..61eeab3 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -623,6 +623,14 @@
         void setAnimationTransaction();
         void setEarlyWakeupStart();
         void setEarlyWakeupEnd();
+
+        /**
+         * Strip the transaction of all permissioned requests, required when
+         * accepting transactions across process boundaries.
+         *
+         * TODO (b/213644870): Remove all permissioned things from Transaction
+         */
+        void sanitize();
     };
 
     status_t clearLayerFrameStats(const sp<IBinder>& token) const;
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index 6f1263b..83e9858 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -987,6 +987,82 @@
     EXPECT_EQ(surface->consumeEvent(100), nullptr);
 }
 
+/**
+ * If a cropped layer's touchable region is replaced with a null crop, it should receive input in
+ * its own crop.
+ */
+TEST_F(InputSurfacesTest, cropped_container_replaces_touchable_region_with_null_crop) {
+    std::unique_ptr<InputSurface> parentContainer =
+            InputSurface::makeContainerInputSurface(mComposerClient, 0, 0);
+    std::unique_ptr<InputSurface> containerSurface =
+            InputSurface::makeContainerInputSurface(mComposerClient, 100, 100);
+    containerSurface->doTransaction(
+            [&](auto &t, auto &sc) { t.reparent(sc, parentContainer->mSurfaceControl); });
+    containerSurface->mInputInfo.replaceTouchableRegionWithCrop = true;
+    containerSurface->mInputInfo.touchableRegionCropHandle = nullptr;
+    parentContainer->showAt(10, 10, Rect(0, 0, 20, 20));
+    containerSurface->showAt(10, 10, Rect(0, 0, 5, 5));
+
+    // Receives events inside its own crop
+    injectTap(21, 21);
+    containerSurface->expectTap(1, 1); // Event is in layer space
+
+    // Does not receive events outside its crop
+    injectTap(26, 26);
+    EXPECT_EQ(containerSurface->consumeEvent(100), nullptr);
+}
+
+/**
+ * If an un-cropped layer's touchable region is replaced with a null crop, it should receive input
+ * in its parent's touchable region. The input events should be in the layer's coordinate space.
+ */
+TEST_F(InputSurfacesTest, uncropped_container_replaces_touchable_region_with_null_crop) {
+    std::unique_ptr<InputSurface> parentContainer =
+            InputSurface::makeContainerInputSurface(mComposerClient, 0, 0);
+    std::unique_ptr<InputSurface> containerSurface =
+            InputSurface::makeContainerInputSurface(mComposerClient, 100, 100);
+    containerSurface->doTransaction(
+            [&](auto &t, auto &sc) { t.reparent(sc, parentContainer->mSurfaceControl); });
+    containerSurface->mInputInfo.replaceTouchableRegionWithCrop = true;
+    containerSurface->mInputInfo.touchableRegionCropHandle = nullptr;
+    parentContainer->showAt(10, 10, Rect(0, 0, 20, 20));
+    containerSurface->showAt(10, 10, Rect::INVALID_RECT);
+
+    // Receives events inside parent bounds
+    injectTap(21, 21);
+    containerSurface->expectTap(1, 1); // Event is in layer space
+
+    // Does not receive events outside parent bounds
+    injectTap(31, 31);
+    EXPECT_EQ(containerSurface->consumeEvent(100), nullptr);
+}
+
+/**
+ * If a layer's touchable region is replaced with a layer crop, it should receive input in the crop
+ * layer's bounds. The input events should be in the layer's coordinate space.
+ */
+TEST_F(InputSurfacesTest, replace_touchable_region_with_crop) {
+    std::unique_ptr<InputSurface> cropLayer =
+            InputSurface::makeContainerInputSurface(mComposerClient, 0, 0);
+    cropLayer->showAt(50, 50, Rect(0, 0, 20, 20));
+
+    std::unique_ptr<InputSurface> containerSurface =
+            InputSurface::makeContainerInputSurface(mComposerClient, 100, 100);
+    containerSurface->mInputInfo.replaceTouchableRegionWithCrop = true;
+    containerSurface->mInputInfo.touchableRegionCropHandle =
+            cropLayer->mSurfaceControl->getHandle();
+    containerSurface->showAt(10, 10, Rect::INVALID_RECT);
+
+    // Receives events inside crop layer bounds
+    injectTap(51, 51);
+    containerSurface->expectTap(41, 41); // Event is in layer space
+
+    // Does not receive events outside crop layer bounds
+    injectTap(21, 21);
+    injectTap(71, 71);
+    EXPECT_EQ(containerSurface->consumeEvent(100), nullptr);
+}
+
 class MultiDisplayTests : public InputSurfacesTest {
 public:
     MultiDisplayTests() : InputSurfacesTest() { ProcessState::self()->startThreadPool(); }
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index e73c3b8..930d819 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -35,6 +35,7 @@
 
 cc_library {
     name: "libinput",
+    cpp_std: "c++20",
     host_supported: true,
     cflags: [
         "-Wall",
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index ac84627..0bee1b6 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -252,6 +252,13 @@
     mLights.insert_or_assign(info.id, info);
 }
 
+void InputDeviceInfo::setKeyboardType(int32_t keyboardType) {
+    static_assert(AINPUT_KEYBOARD_TYPE_NONE < AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
+    static_assert(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC < AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    // There can be multiple subdevices with different keyboard types, set it to the highest type
+    mKeyboardType = std::max(mKeyboardType, keyboardType);
+}
+
 std::vector<InputDeviceSensorInfo> InputDeviceInfo::getSensors() {
     std::vector<InputDeviceSensorInfo> infos;
     infos.reserve(mSensors.size());
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index a065ce2..6195052 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -1318,10 +1318,6 @@
     return result;
 }
 
-bool InputConsumer::hasDeferredEvent() const {
-    return mMsgDeferred;
-}
-
 bool InputConsumer::hasPendingBatch() const {
     return !mBatches.empty();
 }
diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl
index 829bbdd..265cbf0 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -95,4 +95,7 @@
          */
         INTERCEPTS_STYLUS = 0x00000040,
     }
+
+    /* The default pointer acceleration value. */
+    const int DEFAULT_POINTER_ACCELERATION = 3;
 }
diff --git a/libs/input/tests/StructLayout_test.cpp b/libs/input/tests/StructLayout_test.cpp
index b6a9476..1c8658b 100644
--- a/libs/input/tests/StructLayout_test.cpp
+++ b/libs/input/tests/StructLayout_test.cpp
@@ -115,12 +115,9 @@
     static_assert(sizeof(InputMessage::Header) == 8);
 }
 
-/**
- * We cannot use the Body::size() method here because it is not static for
- * the Motion type, where "pointerCount" variable affects the size and can change at runtime.
- */
 void TestBodySize() {
     static_assert(sizeof(InputMessage::Body::Key) == 96);
+    static_assert(sizeof(InputMessage::Body::Motion::Pointer) == 136);
     static_assert(sizeof(InputMessage::Body::Motion) ==
                   offsetof(InputMessage::Body::Motion, pointers) +
                           sizeof(InputMessage::Body::Motion::Pointer) * MAX_POINTERS);
@@ -132,6 +129,38 @@
     // Timeline
     static_assert(GraphicsTimeline::SIZE == 2);
     static_assert(sizeof(InputMessage::Body::Timeline) == 24);
+
+    /**
+     * We cannot use the Body::size() method here because it is not static for
+     * the Motion type, where "pointerCount" variable affects the size and can change at runtime.
+     */
+    static_assert(sizeof(InputMessage::Body) ==
+                  offsetof(InputMessage::Body::Motion, pointers) +
+                          sizeof(InputMessage::Body::Motion::Pointer) * MAX_POINTERS);
+    static_assert(sizeof(InputMessage::Body) == 160 + 136 * 16);
+    static_assert(sizeof(InputMessage::Body) == 2336);
+}
+
+/**
+ * In general, we are sending a variable-length message across the socket, because the number of
+ * pointers varies. When we receive the message, we still need to allocate enough memory for the
+ * entire InputMessage struct. This size is, therefore, the worst case scenario. However, it is
+ * still helpful to compute to get an idea of the sizes that are involved.
+ */
+void TestWorstCaseInputMessageSize() {
+    static_assert(sizeof(InputMessage) == /*header*/ 8 + /*body*/ 2336);
+    static_assert(sizeof(InputMessage) == 2344);
+}
+
+/**
+ * Assuming a single pointer, how big is the message that we are sending across the socket?
+ */
+void CalculateSinglePointerInputMessageSize() {
+    constexpr size_t pointerCount = 1;
+    constexpr size_t bodySize = offsetof(InputMessage::Body::Motion, pointers) +
+            sizeof(InputMessage::Body::Motion::Pointer) * pointerCount;
+    static_assert(bodySize == 160 + 136);
+    static_assert(bodySize == 296); // For the total message size, add the small header
 }
 
 // --- VerifiedInputEvent ---
diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp
index 84daea0..d90ee57 100644
--- a/libs/nativedisplay/AChoreographer.cpp
+++ b/libs/nativedisplay/AChoreographer.cpp
@@ -552,7 +552,7 @@
         const AChoreographerFrameCallbackData* data) {
     return AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(data);
 }
-int64_t AChoreographerFrameCallbackData_routeGetFrameTimelineVsyncId(
+AVsyncId AChoreographerFrameCallbackData_routeGetFrameTimelineVsyncId(
         const AChoreographerFrameCallbackData* data, size_t index) {
     return AChoreographerFrameCallbackData_getFrameTimelineVsyncId(data, index);
 }
@@ -644,7 +644,7 @@
                         "Data is only valid in callback");
     return frameCallbackData->preferredFrameTimelineIndex;
 }
-int64_t AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
+AVsyncId AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
         const AChoreographerFrameCallbackData* data, size_t index) {
     const ChoreographerFrameCallbackDataImpl* frameCallbackData =
             AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data);
diff --git a/libs/nativedisplay/ADisplay.cpp b/libs/nativedisplay/ADisplay.cpp
index 6288194..76b85d6 100644
--- a/libs/nativedisplay/ADisplay.cpp
+++ b/libs/nativedisplay/ADisplay.cpp
@@ -50,11 +50,6 @@
     int32_t height{0};
 
     /**
-     * The display density.
-     */
-    float density{0};
-
-    /**
      * The refresh rate of the display configuration, in frames per second.
      */
     float fps{0.0};
@@ -168,8 +163,8 @@
             const ui::DisplayMode& mode = modes[j];
             modesPerDisplay[i].emplace_back(
                     DisplayConfigImpl{static_cast<size_t>(mode.id), mode.resolution.getWidth(),
-                                      mode.resolution.getHeight(), staticInfo.density,
-                                      mode.refreshRate, mode.sfVsyncOffset, mode.appVsyncOffset});
+                                      mode.resolution.getHeight(), mode.refreshRate,
+                                      mode.sfVsyncOffset, mode.appVsyncOffset});
         }
     }
 
@@ -283,12 +278,6 @@
     return NAME_NOT_FOUND;
 }
 
-float ADisplayConfig_getDensity(ADisplayConfig* config) {
-    CHECK_NOT_NULL(config);
-
-    return reinterpret_cast<DisplayConfigImpl*>(config)->density;
-}
-
 int32_t ADisplayConfig_getWidth(ADisplayConfig* config) {
     CHECK_NOT_NULL(config);
 
diff --git a/libs/nativedisplay/include-private/private/android/choreographer.h b/libs/nativedisplay/include-private/private/android/choreographer.h
index 0a1fcbe..d650c26 100644
--- a/libs/nativedisplay/include-private/private/android/choreographer.h
+++ b/libs/nativedisplay/include-private/private/android/choreographer.h
@@ -65,7 +65,7 @@
         const AChoreographerFrameCallbackData* data);
 size_t AChoreographerFrameCallbackData_routeGetPreferredFrameTimelineIndex(
         const AChoreographerFrameCallbackData* data);
-int64_t AChoreographerFrameCallbackData_routeGetFrameTimelineVsyncId(
+AVsyncId AChoreographerFrameCallbackData_routeGetFrameTimelineVsyncId(
         const AChoreographerFrameCallbackData* data, size_t index);
 int64_t AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentTimeNanos(
         const AChoreographerFrameCallbackData* data, size_t index);
diff --git a/libs/nativedisplay/include/apex/display.h b/libs/nativedisplay/include/apex/display.h
index bd94b55..0f44902 100644
--- a/libs/nativedisplay/include/apex/display.h
+++ b/libs/nativedisplay/include/apex/display.h
@@ -107,11 +107,6 @@
 int ADisplay_getCurrentConfig(ADisplay* display, ADisplayConfig** outConfig);
 
 /**
- * Queries the density for a given display configuration.
- */
-float ADisplayConfig_getDensity(ADisplayConfig* config);
-
-/**
  * Queries the width in pixels for a given display configuration.
  */
 int32_t ADisplayConfig_getWidth(ADisplayConfig* config);
diff --git a/libs/nativedisplay/libnativedisplay.map.txt b/libs/nativedisplay/libnativedisplay.map.txt
index b1b6498..6579313 100644
--- a/libs/nativedisplay/libnativedisplay.map.txt
+++ b/libs/nativedisplay/libnativedisplay.map.txt
@@ -50,7 +50,6 @@
       android::ADisplay_getDisplayType*;
       android::ADisplay_getPreferredWideColorFormat*;
       android::ADisplay_getCurrentConfig*;
-      android::ADisplayConfig_getDensity*;
       android::ADisplayConfig_getWidth*;
       android::ADisplayConfig_getHeight*;
       android::ADisplayConfig_getFps*;
diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp
index cb3361b..2578ee8 100644
--- a/libs/nativewindow/AHardwareBuffer.cpp
+++ b/libs/nativewindow/AHardwareBuffer.cpp
@@ -509,10 +509,6 @@
             ALOGE_IF(log, "AHARDWAREBUFFER_USAGE_SENSOR_DIRECT_DATA requires AHARDWAREBUFFER_FORMAT_BLOB");
             return false;
         }
-        if (desc->usage & AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER) {
-            ALOGE_IF(log, "AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER requires AHARDWAREBUFFER_FORMAT_BLOB");
-            return false;
-        }
     }
 
     if ((desc->usage & (AHARDWAREBUFFER_USAGE_CPU_READ_MASK | AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK)) &&
diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h
index b4cab39..2c51ccd 100644
--- a/libs/renderengine/include/renderengine/DisplaySettings.h
+++ b/libs/renderengine/include/renderengine/DisplaySettings.h
@@ -43,6 +43,9 @@
     // Maximum luminance pulled from the display's HDR capabilities.
     float maxLuminance = 1.0f;
 
+    // Current luminance of the display
+    float currentLuminanceNits = -1.f;
+
     // Output dataspace that will be populated if wide color gamut is used, or
     // DataSpace::UNKNOWN otherwise.
     ui::Dataspace outputDataspace = ui::Dataspace::UNKNOWN;
diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h
index 702e8b0..171cbaa 100644
--- a/libs/renderengine/include/renderengine/LayerSettings.h
+++ b/libs/renderengine/include/renderengine/LayerSettings.h
@@ -313,6 +313,7 @@
     PrintTo(settings.shadow, os);
     *os << "\n    .stretchEffect = ";
     PrintTo(settings.stretchEffect, os);
+    *os << "\n    .whitePointNits = " << settings.whitePointNits;
     *os << "\n}";
 }
 
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index cc90946..763b82d 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -656,6 +656,7 @@
                                  parameters.layerDimmingRatio, 1.f));
         return createLinearEffectShader(parameters.shader, effect, runtimeEffect, colorTransform,
                                         parameters.display.maxLuminance,
+                                        parameters.display.currentLuminanceNits,
                                         parameters.layer.source.buffer.maxLuminanceNits);
     }
     return parameters.shader;
@@ -1103,6 +1104,15 @@
                 paint.setDither(true);
             }
             paint.setAlphaf(layer.alpha);
+
+            if (imageTextureRef->colorType() == kAlpha_8_SkColorType) {
+                LOG_ALWAYS_FATAL_IF(layer.disableBlending, "Cannot disableBlending with A8");
+                float matrix[] = { 0, 0, 0, 0, 0,
+                                   0, 0, 0, 0, 0,
+                                   0, 0, 0, 0, 0,
+                                   0, 0, 0, -1, 1 };
+                paint.setColorFilter(SkColorFilters::Matrix(matrix));
+            }
         } else {
             ATRACE_NAME("DrawColor");
             const auto color = layer.source.solidColor;
@@ -1124,7 +1134,11 @@
             paint.setBlendMode(SkBlendMode::kSrc);
         }
 
-        paint.setColorFilter(displayColorTransform);
+        // A color filter will have been set for an A8 buffer. Do not replace
+        // it with the displayColorTransform, which shouldn't affect A8.
+        if (!paint.getColorFilter()) {
+            paint.setColorFilter(displayColorTransform);
+        }
 
         if (!roundRectClip.isEmpty()) {
             canvas->clipRRect(roundRectClip, true);
diff --git a/libs/renderengine/skia/filters/LinearEffect.cpp b/libs/renderengine/skia/filters/LinearEffect.cpp
index 36305ae..6077c2e 100644
--- a/libs/renderengine/skia/filters/LinearEffect.cpp
+++ b/libs/renderengine/skia/filters/LinearEffect.cpp
@@ -44,14 +44,15 @@
                                          const shaders::LinearEffect& linearEffect,
                                          sk_sp<SkRuntimeEffect> runtimeEffect,
                                          const mat4& colorTransform, float maxDisplayLuminance,
-                                         float maxLuminance) {
+                                         float currentDisplayLuminanceNits, float maxLuminance) {
     ATRACE_CALL();
     SkRuntimeShaderBuilder effectBuilder(runtimeEffect);
 
     effectBuilder.child("child") = shader;
 
-    const auto uniforms = shaders::buildLinearEffectUniforms(linearEffect, colorTransform,
-                                                             maxDisplayLuminance, maxLuminance);
+    const auto uniforms =
+            shaders::buildLinearEffectUniforms(linearEffect, colorTransform, maxDisplayLuminance,
+                                               currentDisplayLuminanceNits, maxLuminance);
 
     for (const auto& uniform : uniforms) {
         effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
diff --git a/libs/renderengine/skia/filters/LinearEffect.h b/libs/renderengine/skia/filters/LinearEffect.h
index 8eb6670..e0a556b 100644
--- a/libs/renderengine/skia/filters/LinearEffect.h
+++ b/libs/renderengine/skia/filters/LinearEffect.h
@@ -37,13 +37,14 @@
 // matrix transforming from linear XYZ to linear RGB immediately before OETF.
 // We also provide additional HDR metadata upon creating the shader:
 // * The max display luminance is the max luminance of the physical display in nits
+// * The current luminance of the physical display in nits
 // * The max luminance is provided as the max luminance for the buffer, either from the SMPTE 2086
 // or as the max light level from the CTA 861.3 standard.
 sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> inputShader,
                                          const shaders::LinearEffect& linearEffect,
                                          sk_sp<SkRuntimeEffect> runtimeEffect,
                                          const mat4& colorTransform, float maxDisplayLuminance,
-                                         float maxLuminance);
+                                         float currentDisplayLuminanceNits, float maxLuminance);
 } // namespace skia
 } // namespace renderengine
 } // namespace android
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 2a25b0b..612a0aa 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -218,14 +218,35 @@
         uint8_t* pixels;
         buffer->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
                                   reinterpret_cast<void**>(&pixels));
-        pixels[0] = color.r;
-        pixels[1] = color.g;
-        pixels[2] = color.b;
-        pixels[3] = color.a;
+        for (uint32_t j = 0; j < height; j++) {
+            uint8_t* dst = pixels + (buffer->getBuffer()->getStride() * j * 4);
+            for (uint32_t i = 0; i < width; i++) {
+                dst[0] = color.r;
+                dst[1] = color.g;
+                dst[2] = color.b;
+                dst[3] = color.a;
+                dst += 4;
+            }
+        }
         buffer->getBuffer()->unlock();
         return buffer;
     }
 
+    std::shared_ptr<renderengine::ExternalTexture> allocateR8Buffer(int width, int height) {
+        auto buffer = new GraphicBuffer(width, height, android::PIXEL_FORMAT_R_8, 1,
+                                        GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN |
+                                                GRALLOC_USAGE_HW_TEXTURE,
+                                        "r8");
+        if (buffer->initCheck() != 0) {
+            // Devices are not required to support R8.
+            return nullptr;
+        }
+        return std::make_shared<
+                renderengine::impl::ExternalTexture>(std::move(buffer), *mRE,
+                                                     renderengine::impl::ExternalTexture::Usage::
+                                                             READABLE);
+    }
+
     RenderEngineTest() {
         const ::testing::TestInfo* const test_info =
                 ::testing::UnitTest::GetInstance()->current_test_info();
@@ -2541,6 +2562,66 @@
 
     expectBufferColor(Rect(kGreyLevels, 1), generator, 2);
 }
+
+TEST_P(RenderEngineTest, r8_behaves_as_mask) {
+    if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
+        return;
+    }
+
+    initializeRenderEngine();
+
+    const auto r8Buffer = allocateR8Buffer(2, 1);
+    if (!r8Buffer) {
+        return;
+    }
+    {
+        uint8_t* pixels;
+        r8Buffer->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                    reinterpret_cast<void**>(&pixels));
+        // This will be drawn on top of a green buffer. We'll verify that 255
+        // results in keeping the original green and 0 results in black.
+        pixels[0] = 0;
+        pixels[1] = 255;
+        r8Buffer->getBuffer()->unlock();
+    }
+
+    const auto rect = Rect(0, 0, 2, 1);
+    const renderengine::DisplaySettings display{
+            .physicalDisplay = rect,
+            .clip = rect,
+            .outputDataspace = ui::Dataspace::SRGB,
+    };
+
+    const auto greenBuffer = allocateAndFillSourceBuffer(2, 1, ubyte4(0, 255, 0, 255));
+    const renderengine::LayerSettings greenLayer{
+            .geometry.boundaries = rect.toFloatRect(),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = greenBuffer,
+                                    },
+                    },
+            .alpha = 1.0f,
+    };
+    const renderengine::LayerSettings r8Layer{
+            .geometry.boundaries = rect.toFloatRect(),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = r8Buffer,
+                                    },
+                    },
+            .alpha = 1.0f,
+    };
+
+    std::vector<renderengine::LayerSettings> layers{greenLayer, r8Layer};
+    invokeDraw(display, layers);
+
+    expectBufferColor(Rect(0, 0, 1, 1), 0,   0, 0, 255);
+    expectBufferColor(Rect(1, 0, 2, 1), 0, 255, 0, 255);
+}
 } // namespace renderengine
 } // namespace android
 
diff --git a/libs/shaders/include/shaders/shaders.h b/libs/shaders/include/shaders/shaders.h
index 712a27a..43828cc 100644
--- a/libs/shaders/include/shaders/shaders.h
+++ b/libs/shaders/include/shaders/shaders.h
@@ -100,6 +100,7 @@
 std::vector<tonemap::ShaderUniform> buildLinearEffectUniforms(const LinearEffect& linearEffect,
                                                               const mat4& colorTransform,
                                                               float maxDisplayLuminance,
+                                                              float currentDisplayLuminanceNits,
                                                               float maxLuminance);
 
 } // namespace android::shaders
diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp
index 6019c4a..4d88d5d 100644
--- a/libs/shaders/shaders.cpp
+++ b/libs/shaders/shaders.cpp
@@ -463,6 +463,7 @@
 std::vector<tonemap::ShaderUniform> buildLinearEffectUniforms(const LinearEffect& linearEffect,
                                                               const mat4& colorTransform,
                                                               float maxDisplayLuminance,
+                                                              float currentDisplayLuminanceNits,
                                                               float maxLuminance) {
     std::vector<tonemap::ShaderUniform> uniforms;
     if (linearEffect.inputDataspace == linearEffect.outputDataspace) {
@@ -480,6 +481,7 @@
     }
 
     tonemap::Metadata metadata{.displayMaxLuminance = maxDisplayLuminance,
+                               .currentDisplayLuminanceNits = currentDisplayLuminanceNits,
                                // If the input luminance is unknown, use display luminance (aka,
                                // no-op any luminance changes)
                                // This will be the case for eg screenshots in addition to
diff --git a/libs/tonemap/include/tonemap/tonemap.h b/libs/tonemap/include/tonemap/tonemap.h
index bd7b72d..6233e6c 100644
--- a/libs/tonemap/include/tonemap/tonemap.h
+++ b/libs/tonemap/include/tonemap/tonemap.h
@@ -42,7 +42,11 @@
 // This metadata should not be used for manipulating the source code of the shader program directly,
 // as otherwise caching by other system of these shaders may break.
 struct Metadata {
+    // The maximum luminance of the display in nits
     float displayMaxLuminance = 0.0;
+    // The current luminance of the display in nits
+    float currentDisplayLuminanceNits = 0.0;
+    // The maximum luminance of the content in nits
     float contentMaxLuminance = 0.0;
 };
 
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index 980fc8b..f5a22ec 100644
--- a/libs/ui/Android.bp
+++ b/libs/ui/Android.bp
@@ -128,6 +128,7 @@
     srcs: [
         "DebugUtils.cpp",
         "DeviceProductInfo.cpp",
+        "DisplayIdentification.cpp",
         "DisplayMode.cpp",
         "DynamicDisplayInfo.cpp",
         "Fence.cpp",
@@ -165,6 +166,7 @@
         "android.hardware.graphics.allocator@2.0",
         "android.hardware.graphics.allocator@3.0",
         "android.hardware.graphics.allocator@4.0",
+        "android.hardware.graphics.allocator-V1-ndk",
         "android.hardware.graphics.common-V3-ndk",
         "android.hardware.graphics.common@1.2",
         "android.hardware.graphics.mapper@2.0",
@@ -172,6 +174,7 @@
         "android.hardware.graphics.mapper@3.0",
         "android.hardware.graphics.mapper@4.0",
         "libbase",
+        "libbinder_ndk",
         "libcutils",
         "libgralloctypes",
         "libhidlbase",
@@ -188,6 +191,7 @@
     ],
 
     static_libs: [
+        "libaidlcommonsupport",
         "libarect",
         "libgrallocusage",
         "libmath",
diff --git a/services/surfaceflinger/DisplayHardware/DisplayIdentification.cpp b/libs/ui/DisplayIdentification.cpp
similarity index 86%
rename from services/surfaceflinger/DisplayHardware/DisplayIdentification.cpp
rename to libs/ui/DisplayIdentification.cpp
index 83c2b2e..16ed82a 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayIdentification.cpp
+++ b/libs/ui/DisplayIdentification.cpp
@@ -24,12 +24,63 @@
 
 #include <log/log.h>
 
-#include "DisplayIdentification.h"
-#include "Hash.h"
+#include <ui/DisplayIdentification.h>
 
 namespace android {
 namespace {
 
+template <class T>
+inline T load(const void* p) {
+    static_assert(std::is_integral<T>::value, "T must be integral");
+
+    T r;
+    std::memcpy(&r, p, sizeof(r));
+    return r;
+}
+
+uint64_t rotateByAtLeast1(uint64_t val, uint8_t shift) {
+    return (val >> shift) | (val << (64 - shift));
+}
+
+uint64_t shiftMix(uint64_t val) {
+    return val ^ (val >> 47);
+}
+
+uint64_t hash64Len16(uint64_t u, uint64_t v) {
+    constexpr uint64_t kMul = 0x9ddfea08eb382d69;
+    uint64_t a = (u ^ v) * kMul;
+    a ^= (a >> 47);
+    uint64_t b = (v ^ a) * kMul;
+    b ^= (b >> 47);
+    b *= kMul;
+    return b;
+}
+
+uint64_t hash64Len0To16(const char* s, uint64_t len) {
+    constexpr uint64_t k2 = 0x9ae16a3b2f90404f;
+    constexpr uint64_t k3 = 0xc949d7c7509e6557;
+
+    if (len > 8) {
+        const uint64_t a = load<uint64_t>(s);
+        const uint64_t b = load<uint64_t>(s + len - 8);
+        return hash64Len16(a, rotateByAtLeast1(b + len, static_cast<uint8_t>(len))) ^ b;
+    }
+    if (len >= 4) {
+        const uint32_t a = load<uint32_t>(s);
+        const uint32_t b = load<uint32_t>(s + len - 4);
+        return hash64Len16(len + (a << 3), b);
+    }
+    if (len > 0) {
+        const unsigned char a = static_cast<unsigned char>(s[0]);
+        const unsigned char b = static_cast<unsigned char>(s[len >> 1]);
+        const unsigned char c = static_cast<unsigned char>(s[len - 1]);
+        const uint32_t y = static_cast<uint32_t>(a) + (static_cast<uint32_t>(b) << 8);
+        const uint32_t z = static_cast<uint32_t>(len) + (static_cast<uint32_t>(c) << 2);
+        return shiftMix(y * k2 ^ z * k3) * k2;
+    }
+    return k2;
+}
+
 using byte_view = std::basic_string_view<uint8_t>;
 
 constexpr size_t kEdidBlockSize = 128;
@@ -339,5 +390,13 @@
     return PhysicalDisplayId::fromEdid(0, kVirtualEdidManufacturerId, id);
 }
 
-} // namespace android
+uint64_t cityHash64Len0To16(std::string_view sv) {
+    auto len = sv.length();
+    if (len > 16) {
+        ALOGE("%s called with length %zu. Only hashing the first 16 chars", __FUNCTION__, len);
+        len = 16;
+    }
+    return hash64Len0To16(sv.data(), len);
+}
 
+} // namespace android
\ No newline at end of file
diff --git a/libs/ui/Gralloc2.cpp b/libs/ui/Gralloc2.cpp
index 040a62b..f23f10a 100644
--- a/libs/ui/Gralloc2.cpp
+++ b/libs/ui/Gralloc2.cpp
@@ -110,6 +110,15 @@
               descriptorInfo->usage & ~validUsageBits);
         return BAD_VALUE;
     }
+
+    // Gralloc2 implementations never understand non-BLOB with GPU_DATA_BUFFER
+    // and do not reliably reject it.
+    if (descriptorInfo->usage & BufferUsage::GPU_DATA_BUFFER &&
+        descriptorInfo->format != hardware::graphics::common::V1_1::PixelFormat::BLOB) {
+        ALOGE("gralloc2 does not support non-BLOB pixel formats with GPU_DATA_BUFFER usage");
+        return BAD_VALUE;
+    }
+
     return NO_ERROR;
 }
 
diff --git a/libs/ui/Gralloc3.cpp b/libs/ui/Gralloc3.cpp
index 882674f..15c60bc 100644
--- a/libs/ui/Gralloc3.cpp
+++ b/libs/ui/Gralloc3.cpp
@@ -101,6 +101,15 @@
               descriptorInfo->usage & ~validUsageBits);
         return BAD_VALUE;
     }
+
+    // Gralloc3 implementations never understand non-BLOB with GPU_DATA_BUFFER
+    // and do not reliably reject it.
+    if (descriptorInfo->usage & BufferUsage::GPU_DATA_BUFFER &&
+        descriptorInfo->format != hardware::graphics::common::V1_2::PixelFormat::BLOB) {
+        ALOGE("gralloc3 does not support non-BLOB pixel formats with GPU_DATA_BUFFER usage");
+        return BAD_VALUE;
+    }
+
     return NO_ERROR;
 }
 
diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp
index 3fc99bb..9922d6a 100644
--- a/libs/ui/Gralloc4.cpp
+++ b/libs/ui/Gralloc4.cpp
@@ -16,6 +16,12 @@
 
 #define LOG_TAG "Gralloc4"
 
+#include <aidl/android/hardware/graphics/allocator/AllocationError.h>
+#include <aidl/android/hardware/graphics/allocator/AllocationResult.h>
+#include <aidl/android/hardware/graphics/common/BufferUsage.h>
+#include <aidlcommonsupport/NativeHandle.h>
+#include <android/binder_enums.h>
+#include <android/binder_manager.h>
 #include <hidl/ServiceManagement.h>
 #include <hwbinder/IPCThreadState.h>
 #include <ui/Gralloc4.h>
@@ -27,16 +33,22 @@
 #include <sync/sync.h>
 #pragma clang diagnostic pop
 
+using aidl::android::hardware::graphics::allocator::AllocationError;
+using aidl::android::hardware::graphics::allocator::AllocationResult;
 using aidl::android::hardware::graphics::common::ExtendableType;
 using aidl::android::hardware::graphics::common::PlaneLayoutComponentType;
 using aidl::android::hardware::graphics::common::StandardMetadataType;
 using android::hardware::hidl_vec;
 using android::hardware::graphics::allocator::V4_0::IAllocator;
 using android::hardware::graphics::common::V1_2::BufferUsage;
+using android::hardware::graphics::common::V1_2::PixelFormat;
 using android::hardware::graphics::mapper::V4_0::BufferDescriptor;
 using android::hardware::graphics::mapper::V4_0::Error;
 using android::hardware::graphics::mapper::V4_0::IMapper;
+using AidlIAllocator = ::aidl::android::hardware::graphics::allocator::IAllocator;
+using AidlBufferUsage = ::aidl::android::hardware::graphics::common::BufferUsage;
 using AidlDataspace = ::aidl::android::hardware::graphics::common::Dataspace;
+using AidlNativeHandle = ::aidl::android::hardware::common::NativeHandle;
 using BufferDump = android::hardware::graphics::mapper::V4_0::IMapper::BufferDump;
 using MetadataDump = android::hardware::graphics::mapper::V4_0::IMapper::MetadataDump;
 using MetadataType = android::hardware::graphics::mapper::V4_0::IMapper::MetadataType;
@@ -48,6 +60,7 @@
 namespace {
 
 static constexpr Error kTransactionError = Error::NO_RESOURCES;
+static const auto kAidlAllocatorServiceName = AidlIAllocator::descriptor + std::string("/default");
 
 uint64_t getValidUsageBits() {
     static const uint64_t validUsageBits = []() -> uint64_t {
@@ -61,6 +74,17 @@
     return validUsageBits;
 }
 
+uint64_t getValidUsageBits41() {
+    static const uint64_t validUsageBits = []() -> uint64_t {
+        uint64_t bits = 0;
+        for (const auto bit : ndk::enum_range<AidlBufferUsage>{}) {
+            bits |= static_cast<int64_t>(bit);
+        }
+        return bits;
+    }();
+    return validUsageBits;
+}
+
 static inline IMapper::Rect sGralloc4Rect(const Rect& rect) {
     IMapper::Rect outRect{};
     outRect.left = rect.left;
@@ -69,9 +93,51 @@
     outRect.height = rect.height();
     return outRect;
 }
-static inline void sBufferDescriptorInfo(std::string name, uint32_t width, uint32_t height,
-                                         PixelFormat format, uint32_t layerCount, uint64_t usage,
-                                         IMapper::BufferDescriptorInfo* outDescriptorInfo) {
+
+// See if gralloc "4.1" is available.
+static bool hasIAllocatorAidl() {
+    // Avoid re-querying repeatedly for this information;
+    static bool sHasIAllocatorAidl = []() -> bool {
+        // TODO: Enable after landing sepolicy changes
+        if constexpr ((true)) return false;
+
+        if (__builtin_available(android 31, *)) {
+            return AServiceManager_isDeclared(kAidlAllocatorServiceName.c_str());
+        }
+        return false;
+    }();
+    return sHasIAllocatorAidl;
+}
+
+// Determines whether the passed info is compatible with the mapper.
+static status_t validateBufferDescriptorInfo(IMapper::BufferDescriptorInfo* descriptorInfo) {
+    uint64_t validUsageBits = getValidUsageBits();
+    if (hasIAllocatorAidl()) {
+        validUsageBits |= getValidUsageBits41();
+    }
+
+    if (descriptorInfo->usage & ~validUsageBits) {
+        ALOGE("buffer descriptor contains invalid usage bits 0x%" PRIx64,
+              descriptorInfo->usage & ~validUsageBits);
+        return BAD_VALUE;
+    }
+
+    // Combinations that are only allowed with gralloc 4.1.
+    // Previous grallocs must be protected from this.
+    if (!hasIAllocatorAidl() &&
+            descriptorInfo->format != hardware::graphics::common::V1_2::PixelFormat::BLOB &&
+            descriptorInfo->usage & BufferUsage::GPU_DATA_BUFFER) {
+        ALOGE("non-BLOB pixel format with GPU_DATA_BUFFER usage is not supported prior to gralloc 4.1");
+        return BAD_VALUE;
+    }
+
+    return NO_ERROR;
+}
+
+static inline status_t sBufferDescriptorInfo(std::string name, uint32_t width, uint32_t height,
+                                             PixelFormat format, uint32_t layerCount,
+                                             uint64_t usage,
+                                             IMapper::BufferDescriptorInfo* outDescriptorInfo) {
     outDescriptorInfo->name = name;
     outDescriptorInfo->width = width;
     outDescriptorInfo->height = height;
@@ -79,6 +145,8 @@
     outDescriptorInfo->format = static_cast<hardware::graphics::common::V1_2::PixelFormat>(format);
     outDescriptorInfo->usage = usage;
     outDescriptorInfo->reservedSize = 0;
+
+    return validateBufferDescriptorInfo(outDescriptorInfo);
 }
 
 } // anonymous namespace
@@ -102,18 +170,6 @@
     return mMapper != nullptr;
 }
 
-status_t Gralloc4Mapper::validateBufferDescriptorInfo(
-        IMapper::BufferDescriptorInfo* descriptorInfo) const {
-    uint64_t validUsageBits = getValidUsageBits();
-
-    if (descriptorInfo->usage & ~validUsageBits) {
-        ALOGE("buffer descriptor contains invalid usage bits 0x%" PRIx64,
-              descriptorInfo->usage & ~validUsageBits);
-        return BAD_VALUE;
-    }
-    return NO_ERROR;
-}
-
 status_t Gralloc4Mapper::createDescriptor(void* bufferDescriptorInfo,
                                           void* outBufferDescriptor) const {
     IMapper::BufferDescriptorInfo* descriptorInfo =
@@ -166,8 +222,10 @@
                                             uint32_t layerCount, uint64_t usage,
                                             uint32_t stride) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
-    sBufferDescriptorInfo("validateBufferSize", width, height, format, layerCount, usage,
-                          &descriptorInfo);
+    if (auto error = sBufferDescriptorInfo("validateBufferSize", width, height, format, layerCount,
+                                           usage, &descriptorInfo) != OK) {
+        return error;
+    }
 
     auto buffer = const_cast<native_handle_t*>(bufferHandle);
     auto ret = mMapper->validateBufferSize(buffer, descriptorInfo, stride);
@@ -386,7 +444,7 @@
             if (fd >= 0) {
                 releaseFence = fd;
             } else {
-                ALOGD("failed to dup unlock release fence");
+                ALOGW("failed to dup unlock release fence");
                 sync_wait(fenceHandle->data[0], -1);
             }
         }
@@ -407,7 +465,12 @@
                                      uint32_t layerCount, uint64_t usage,
                                      bool* outSupported) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
-    sBufferDescriptorInfo("isSupported", width, height, format, layerCount, usage, &descriptorInfo);
+    if (auto error = sBufferDescriptorInfo("isSupported", width, height, format, layerCount, usage,
+                                           &descriptorInfo) != OK) {
+        // Usage isn't known to the HAL or otherwise failed validation.
+        *outSupported = false;
+        return OK;
+    }
 
     Error error;
     auto ret = mMapper->isSupported(descriptorInfo,
@@ -650,7 +713,10 @@
     }
 
     IMapper::BufferDescriptorInfo descriptorInfo;
-    sBufferDescriptorInfo("getDefault", width, height, format, layerCount, usage, &descriptorInfo);
+    if (auto error = sBufferDescriptorInfo("getDefault", width, height, format, layerCount, usage,
+                                           &descriptorInfo) != OK) {
+        return error;
+    }
 
     hidl_vec<uint8_t> vec;
     Error error;
@@ -1070,6 +1136,13 @@
         ALOGW("allocator 4.x is not supported");
         return;
     }
+    if (__builtin_available(android 31, *)) {
+        if (hasIAllocatorAidl()) {
+            mAidlAllocator = AidlIAllocator::fromBinder(ndk::SpAIBinder(
+                    AServiceManager_waitForService(kAidlAllocatorServiceName.c_str())));
+            ALOGE_IF(!mAidlAllocator, "AIDL IAllocator declared but failed to get service");
+        }
+    }
 }
 
 bool Gralloc4Allocator::isLoaded() const {
@@ -1085,7 +1158,10 @@
                                      uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
                                      buffer_handle_t* outBufferHandles, bool importBuffers) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
-    sBufferDescriptorInfo(requestorName, width, height, format, layerCount, usage, &descriptorInfo);
+    if (auto error = sBufferDescriptorInfo(requestorName, width, height, format, layerCount, usage,
+                                           &descriptorInfo) != OK) {
+        return error;
+    }
 
     BufferDescriptor descriptor;
     status_t error = mMapper.createDescriptor(static_cast<void*>(&descriptorInfo),
@@ -1094,6 +1170,52 @@
         return error;
     }
 
+    if (mAidlAllocator) {
+        AllocationResult result;
+        auto status = mAidlAllocator->allocate(descriptor, bufferCount, &result);
+        if (!status.isOk()) {
+            error = status.getExceptionCode();
+            if (error == EX_SERVICE_SPECIFIC) {
+                error = status.getServiceSpecificError();
+            }
+            if (error == OK) {
+                error = UNKNOWN_ERROR;
+            }
+        } else {
+            if (importBuffers) {
+                for (uint32_t i = 0; i < bufferCount; i++) {
+                    error = mMapper.importBuffer(makeFromAidl(result.buffers[i]),
+                                                 &outBufferHandles[i]);
+                    if (error != NO_ERROR) {
+                        for (uint32_t j = 0; j < i; j++) {
+                            mMapper.freeBuffer(outBufferHandles[j]);
+                            outBufferHandles[j] = nullptr;
+                        }
+                        break;
+                    }
+                }
+            } else {
+                for (uint32_t i = 0; i < bufferCount; i++) {
+                    outBufferHandles[i] = dupFromAidl(result.buffers[i]);
+                    if (!outBufferHandles[i]) {
+                        for (uint32_t j = 0; j < i; j++) {
+                            auto buffer = const_cast<native_handle_t*>(outBufferHandles[j]);
+                            native_handle_close(buffer);
+                            native_handle_delete(buffer);
+                            outBufferHandles[j] = nullptr;
+                        }
+                    }
+                }
+            }
+        }
+        *outStride = result.stride;
+        // Release all the resources held by AllocationResult (specifically any remaining FDs)
+        result = {};
+        // make sure the kernel driver sees BC_FREE_BUFFER and closes the fds now
+        hardware::IPCThreadState::self()->flushCommands();
+        return error;
+    }
+
     auto ret = mAllocator->allocate(descriptor, bufferCount,
                                     [&](const auto& tmpError, const auto& tmpStride,
                                         const auto& tmpBuffers) {
diff --git a/libs/ui/StaticDisplayInfo.cpp b/libs/ui/StaticDisplayInfo.cpp
index b66b281..03d15e4 100644
--- a/libs/ui/StaticDisplayInfo.cpp
+++ b/libs/ui/StaticDisplayInfo.cpp
@@ -29,7 +29,8 @@
     return FlattenableHelpers::getFlattenedSize(connectionType) +
             FlattenableHelpers::getFlattenedSize(density) +
             FlattenableHelpers::getFlattenedSize(secure) +
-            FlattenableHelpers::getFlattenedSize(deviceProductInfo);
+            FlattenableHelpers::getFlattenedSize(deviceProductInfo) +
+            FlattenableHelpers::getFlattenedSize(installOrientation);
 }
 
 status_t StaticDisplayInfo::flatten(void* buffer, size_t size) const {
@@ -40,6 +41,7 @@
     RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, density));
     RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, secure));
     RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, deviceProductInfo));
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, installOrientation));
     return OK;
 }
 
@@ -48,6 +50,7 @@
     RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &density));
     RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &secure));
     RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &deviceProductInfo));
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &installOrientation));
     return OK;
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/DisplayIdentification.h b/libs/ui/include/ui/DisplayIdentification.h
similarity index 93%
rename from services/surfaceflinger/DisplayHardware/DisplayIdentification.h
rename to libs/ui/include/ui/DisplayIdentification.h
index fbea4e5..fc9c0f4 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayIdentification.h
+++ b/libs/ui/include/ui/DisplayIdentification.h
@@ -31,7 +31,6 @@
 
 namespace android {
 
-
 using DisplayIdentificationData = std::vector<uint8_t>;
 
 struct DisplayIdentificationInfo {
@@ -81,5 +80,7 @@
 
 PhysicalDisplayId getVirtualDisplayId(uint32_t id);
 
-} // namespace android
+// CityHash64 implementation that only hashes at most the first 16 characters of the given string.
+uint64_t cityHash64Len0To16(std::string_view sv);
 
+} // namespace android
diff --git a/libs/ui/include/ui/Gralloc4.h b/libs/ui/include/ui/Gralloc4.h
index 62f9e4a..fe38709 100644
--- a/libs/ui/include/ui/Gralloc4.h
+++ b/libs/ui/include/ui/Gralloc4.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_UI_GRALLOC4_H
 #define ANDROID_UI_GRALLOC4_H
 
+#include <aidl/android/hardware/graphics/allocator/IAllocator.h>
 #include <android/hardware/graphics/allocator/4.0/IAllocator.h>
 #include <android/hardware/graphics/common/1.1/types.h>
 #include <android/hardware/graphics/mapper/4.0/IMapper.h>
@@ -154,10 +155,6 @@
 private:
     friend class GraphicBufferAllocator;
 
-    // Determines whether the passed info is compatible with the mapper.
-    status_t validateBufferDescriptorInfo(
-            hardware::graphics::mapper::V4_0::IMapper::BufferDescriptorInfo* descriptorInfo) const;
-
     template <class T>
     using DecodeFunction = status_t (*)(const hardware::hidl_vec<uint8_t>& input, T* output);
 
@@ -204,6 +201,8 @@
 private:
     const Gralloc4Mapper& mMapper;
     sp<hardware::graphics::allocator::V4_0::IAllocator> mAllocator;
+    // Optional "4.1" allocator
+    std::shared_ptr<aidl::android::hardware::graphics::allocator::IAllocator> mAidlAllocator;
 };
 
 } // namespace android
diff --git a/libs/ui/include/ui/StaticDisplayInfo.h b/libs/ui/include/ui/StaticDisplayInfo.h
index e86ca29..cc7c869 100644
--- a/libs/ui/include/ui/StaticDisplayInfo.h
+++ b/libs/ui/include/ui/StaticDisplayInfo.h
@@ -19,6 +19,7 @@
 #include <optional>
 
 #include <ui/DeviceProductInfo.h>
+#include <ui/Rotation.h>
 #include <utils/Flattenable.h>
 
 namespace android::ui {
@@ -31,6 +32,7 @@
     float density = 0.f;
     bool secure = false;
     std::optional<DeviceProductInfo> deviceProductInfo;
+    Rotation installOrientation = ROTATION_0;
 
     bool isFixedSize() const { return false; }
     size_t getFlattenedSize() const;
diff --git a/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp b/libs/ui/tests/DisplayIdentification_test.cpp
similarity index 98%
rename from services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp
rename to libs/ui/tests/DisplayIdentification_test.cpp
index cd4a5c9..736979a 100644
--- a/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp
+++ b/libs/ui/tests/DisplayIdentification_test.cpp
@@ -24,12 +24,12 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
-#include "DisplayHardware/DisplayIdentification.h"
-#include "DisplayHardware/Hash.h"
+#include <ui/DisplayIdentification.h>
 
 using ::testing::ElementsAre;
 
 namespace android {
+
 namespace {
 
 const unsigned char kInternalEdid[] =
diff --git a/services/audiomanager/Android.bp b/services/audiomanager/Android.bp
index e6fb2c3..d11631b 100644
--- a/services/audiomanager/Android.bp
+++ b/services/audiomanager/Android.bp
@@ -7,7 +7,7 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
-cc_library_shared {
+cc_library {
     name: "libaudiomanager",
 
     srcs: [
diff --git a/services/batteryservice/include/batteryservice/BatteryService.h b/services/batteryservice/include/batteryservice/BatteryService.h
index 1e8eb1e..178bc29 100644
--- a/services/batteryservice/include/batteryservice/BatteryService.h
+++ b/services/batteryservice/include/batteryservice/BatteryService.h
@@ -40,6 +40,7 @@
     bool chargerAcOnline;
     bool chargerUsbOnline;
     bool chargerWirelessOnline;
+    bool chargerDockOnline;
     int maxChargingCurrent;
     int maxChargingVoltage;
     int batteryStatus;
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 3d85bef..9b72ff4 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -15,6 +15,9 @@
       "name": "inputflinger_tests"
     },
     {
+      "name": "libpalmrejection_test"
+    },
+    {
       "name": "InputTests"
     },
     {
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 3f3c0db..7a3d6c5 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -215,7 +215,7 @@
         return false;
     }
     if (pointerCount < 1 || pointerCount > MAX_POINTERS) {
-        ALOGE("Motion event has invalid pointer count %zu; value must be between 1 and %d.",
+        ALOGE("Motion event has invalid pointer count %zu; value must be between 1 and %zu.",
               pointerCount, MAX_POINTERS);
         return false;
     }
@@ -5645,6 +5645,15 @@
             return;
         }
 
+        if (enabled) {
+            if (std::find(mIneligibleDisplaysForPointerCapture.begin(),
+                          mIneligibleDisplaysForPointerCapture.end(),
+                          mFocusedDisplayId) != mIneligibleDisplaysForPointerCapture.end()) {
+                ALOGW("Ignoring request to enable Pointer Capture: display is not eligible");
+                return;
+            }
+        }
+
         setPointerCaptureLocked(enabled);
     } // release lock
 
@@ -5652,6 +5661,16 @@
     mLooper->wake();
 }
 
+void InputDispatcher::setDisplayEligibilityForPointerCapture(int32_t displayId, bool isEligible) {
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+        std::erase(mIneligibleDisplaysForPointerCapture, displayId);
+        if (!isEligible) {
+            mIneligibleDisplaysForPointerCapture.push_back(displayId);
+        }
+    } // release lock
+}
+
 std::optional<int32_t> InputDispatcher::findGestureMonitorDisplayByTokenLocked(
         const sp<IBinder>& token) {
     for (const auto& it : mGestureMonitorsByDisplay) {
@@ -6311,6 +6330,8 @@
         // Call focus resolver to clean up stale requests. This must be called after input windows
         // have been removed for the removed display.
         mFocusResolver.displayRemoved(displayId);
+        // Reset pointer capture eligibility, regardless of previous state.
+        std::erase(mIneligibleDisplaysForPointerCapture, displayId);
     } // release lock
 
     // Wake up poll loop since it may need to make new input dispatching choices.
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index cbb7bad..7564839 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -136,6 +136,7 @@
     status_t pilferPointers(const sp<IBinder>& token) override;
     void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled) override;
     bool flushSensor(int deviceId, InputDeviceSensorType sensorType) override;
+    void setDisplayEligibilityForPointerCapture(int displayId, bool isEligible) override;
 
     std::array<uint8_t, 32> sign(const VerifiedInputEvent& event) const;
 
@@ -420,6 +421,10 @@
     // This should be in sync with PointerCaptureChangedEvents dispatched to the input channel.
     sp<IBinder> mWindowTokenWithPointerCapture GUARDED_BY(mLock);
 
+    // Displays that are ineligible for pointer capture.
+    // TODO(b/214621487): Remove or move to a display flag.
+    std::vector<int32_t> mIneligibleDisplaysForPointerCapture GUARDED_BY(mLock);
+
     // Disable Pointer Capture as a result of loss of window focus.
     void disablePointerCaptureForcedLocked() REQUIRES(mLock);
 
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index c469ec3..16a6f16 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -201,6 +201,13 @@
      */
     virtual void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled) = 0;
 
+    /**
+     * Sets the eligibility of a given display to enable pointer capture. If a display is marked
+     * ineligible, all attempts to request pointer capture for windows on that display will fail.
+     *  TODO(b/214621487): Remove or move to a display flag.
+     */
+    virtual void setDisplayEligibilityForPointerCapture(int displayId, bool isEligible) = 0;
+
     /* Flush input device motion sensor.
      *
      * Returns true on success.
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 1aab856..41ecef3 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -17,6 +17,7 @@
 #ifndef _UI_INPUT_READER_BASE_H
 #define _UI_INPUT_READER_BASE_H
 
+#include <android/os/IInputConstants.h>
 #include <input/DisplayViewport.h>
 #include <input/Input.h>
 #include <input/InputDevice.h>
@@ -87,6 +88,8 @@
     virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
             int32_t sw) = 0;
 
+    virtual int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const = 0;
+
     /* Toggle Caps Lock */
     virtual void toggleCapsLockState(int32_t deviceId) = 0;
 
@@ -286,7 +289,10 @@
 
     InputReaderConfiguration()
           : virtualKeyQuietTime(0),
-            pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f, 3.0f),
+            pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f,
+                                             static_cast<float>(
+                                                     android::os::IInputConstants::
+                                                             DEFAULT_POINTER_ACCELERATION)),
             wheelVelocityControlParameters(1.0f, 15.0f, 50.0f, 4.0f),
             pointerGesturesEnabled(true),
             pointerGestureQuietInterval(100 * 1000000LL),            // 100 ms
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index 09a62f3..db67877 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -880,6 +880,42 @@
     return AKEY_STATE_UNKNOWN;
 }
 
+int32_t EventHub::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const {
+    std::scoped_lock _l(mLock);
+
+    Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr || !device->hasValidFd() || device->keyMap.keyCharacterMap == nullptr ||
+        device->keyMap.keyLayoutMap == nullptr) {
+        return AKEYCODE_UNKNOWN;
+    }
+    std::vector<int32_t> scanCodes;
+    device->keyMap.keyLayoutMap->findScanCodesForKey(locationKeyCode, &scanCodes);
+    if (scanCodes.empty()) {
+        ALOGW("Failed to get key code for key location: no scan code maps to key code %d for input"
+              "device %d",
+              locationKeyCode, deviceId);
+        return AKEYCODE_UNKNOWN;
+    }
+    if (scanCodes.size() > 1) {
+        ALOGW("Multiple scan codes map to the same key code %d, returning only the first match",
+              locationKeyCode);
+    }
+    int32_t outKeyCode;
+    status_t mapKeyRes =
+            device->getKeyCharacterMap()->mapKey(scanCodes[0], 0 /*usageCode*/, &outKeyCode);
+    switch (mapKeyRes) {
+        case OK:
+            return outKeyCode;
+        case NAME_NOT_FOUND:
+            // key character map doesn't re-map this scanCode, hence the keyCode remains the same
+            return locationKeyCode;
+        default:
+            ALOGW("Failed to get key code for key location: Key character map returned error %s",
+                  statusToString(mapKeyRes).c_str());
+            return AKEYCODE_UNKNOWN;
+    }
+}
+
 int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const {
     if (sw >= 0 && sw <= SW_MAX) {
         std::scoped_lock _l(mLock);
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 7cff672..a050963 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -309,15 +309,15 @@
                 const auto& displayPort = ports.find(inputPort);
                 if (displayPort != ports.end()) {
                     mAssociatedDisplayPort = std::make_optional(displayPort->second);
+                } else {
+                    const std::unordered_map<std::string, std::string>& displayUniqueIds =
+                            config->uniqueIdAssociations;
+                    const auto& displayUniqueId = displayUniqueIds.find(inputPort);
+                    if (displayUniqueId != displayUniqueIds.end()) {
+                        mAssociatedDisplayUniqueId = displayUniqueId->second;
+                    }
                 }
             }
-            const std::string& inputDeviceName = mIdentifier.name;
-            const std::unordered_map<std::string, std::string>& names =
-                    config->uniqueIdAssociations;
-            const auto& displayUniqueId = names.find(inputDeviceName);
-            if (displayUniqueId != names.end()) {
-                mAssociatedDisplayUniqueId = displayUniqueId->second;
-            }
 
             // If the device was explicitly disabled by the user, it would be present in the
             // "disabledDevices" list. If it is associated with a specific display, and it was not
@@ -338,7 +338,7 @@
                 if (!mAssociatedViewport) {
                     ALOGW("Input device %s should be associated with display %s but the "
                           "corresponding viewport cannot be found",
-                          inputDeviceName.c_str(), mAssociatedDisplayUniqueId->c_str());
+                          getName().c_str(), mAssociatedDisplayUniqueId->c_str());
                     enabled = false;
                 }
             }
@@ -475,6 +475,23 @@
     return result;
 }
 
+int32_t InputDevice::getKeyCodeForKeyLocation(int32_t locationKeyCode) const {
+    std::optional<int32_t> result = first_in_mappers<int32_t>(
+            [locationKeyCode](const InputMapper& mapper) -> std::optional<int32_t> const {
+                if (sourcesMatchMask(mapper.getSources(), AINPUT_SOURCE_KEYBOARD)) {
+                    return std::make_optional(mapper.getKeyCodeForKeyLocation(locationKeyCode));
+                }
+                return std::nullopt;
+            });
+    if (!result) {
+        ALOGE("Failed to get key code for key location: No matching InputMapper with source mask "
+              "KEYBOARD found. The provided input device with id %d has sources %s.",
+              getId(), inputEventSourceToString(getSources()).c_str());
+        return AKEYCODE_UNKNOWN;
+    }
+    return *result;
+}
+
 void InputDevice::vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token) {
     for_each_mapper([sequence, repeat, token](InputMapper& mapper) {
         mapper.vibrate(sequence, repeat, token);
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 564db56..31d331b 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -303,7 +303,7 @@
     device->process(rawEvents, count);
 }
 
-InputDevice* InputReader::findInputDeviceLocked(int32_t deviceId) {
+InputDevice* InputReader::findInputDeviceLocked(int32_t deviceId) const {
     auto deviceIt =
             std::find_if(mDevices.begin(), mDevices.end(), [deviceId](const auto& devicePair) {
                 return devicePair.second->getId() == deviceId;
@@ -589,6 +589,18 @@
     return result;
 }
 
+int32_t InputReader::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const {
+    std::scoped_lock _l(mLock);
+
+    InputDevice* device = findInputDeviceLocked(deviceId);
+    if (device == nullptr) {
+        ALOGW("Failed to get key code for key location: Input device with id %d not found",
+              deviceId);
+        return AKEYCODE_UNKNOWN;
+    }
+    return device->getKeyCodeForKeyLocation(locationKeyCode);
+}
+
 void InputReader::requestRefreshConfiguration(uint32_t changes) {
     std::scoped_lock _l(mLock);
 
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 1f96294..18e912d 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -306,6 +306,7 @@
     virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0;
     virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
                                           int32_t* outValue) const = 0;
+    virtual int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const = 0;
 
     /*
      * Examine key input devices for specific framework keycode support
@@ -482,6 +483,8 @@
     int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override final;
     int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override final;
     int32_t getSwitchState(int32_t deviceId, int32_t sw) const override final;
+    int32_t getKeyCodeForKeyLocation(int32_t deviceId,
+                                     int32_t locationKeyCode) const override final;
     status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
                                   int32_t* outValue) const override final;
 
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 518aaa0..694daa9 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -61,6 +61,9 @@
     inline std::optional<uint8_t> getAssociatedDisplayPort() const {
         return mAssociatedDisplayPort;
     }
+    inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
+        return mAssociatedDisplayUniqueId;
+    }
     inline std::optional<DisplayViewport> getAssociatedViewport() const {
         return mAssociatedViewport;
     }
@@ -84,6 +87,7 @@
     int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
     int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
     int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+    int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const;
     bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, const int32_t* keyCodes,
                                uint8_t* outFlags);
     void vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token);
@@ -216,7 +220,8 @@
     // return the first value returned by a function over every mapper.
     // if all mappers return nullopt, return nullopt.
     template <typename T>
-    inline std::optional<T> first_in_mappers(std::function<std::optional<T>(InputMapper&)> f) {
+    inline std::optional<T> first_in_mappers(
+            std::function<std::optional<T>(InputMapper&)> f) const {
         for (auto& deviceEntry : mDevices) {
             auto& devicePair = deviceEntry.second;
             auto& mappers = devicePair.second;
@@ -312,6 +317,9 @@
     inline int32_t getKeyCodeState(int32_t keyCode) const {
         return mEventHub->getKeyCodeState(mId, keyCode);
     }
+    inline int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const {
+        return mEventHub->getKeyCodeForKeyLocation(mId, locationKeyCode);
+    }
     inline int32_t getSwitchState(int32_t sw) const { return mEventHub->getSwitchState(mId, sw); }
     inline status_t getAbsoluteAxisValue(int32_t code, int32_t* outValue) const {
         return mEventHub->getAbsoluteAxisValue(mId, code, outValue);
@@ -381,6 +389,9 @@
     inline std::optional<uint8_t> getAssociatedDisplayPort() const {
         return mDevice.getAssociatedDisplayPort();
     }
+    inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
+        return mDevice.getAssociatedDisplayUniqueId();
+    }
     inline std::optional<DisplayViewport> getAssociatedViewport() const {
         return mDevice.getAssociatedViewport();
     }
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index fb1d166..daeaa1d 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -69,6 +69,8 @@
     int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, int32_t keyCode) override;
     int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw) override;
 
+    int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override;
+
     void toggleCapsLockState(int32_t deviceId) override;
 
     bool hasKeys(int32_t deviceId, uint32_t sourceMask, size_t numCodes, const int32_t* keyCodes,
@@ -239,7 +241,7 @@
                                      const int32_t* keyCodes, uint8_t* outFlags) REQUIRES(mLock);
 
     // find an InputDevice from an InputDevice id
-    InputDevice* findInputDeviceLocked(int32_t deviceId) REQUIRES(mLock);
+    InputDevice* findInputDeviceLocked(int32_t deviceId) const REQUIRES(mLock);
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index fcb56ef..dc5fcec 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -66,7 +66,7 @@
 
 CursorInputMapper::~CursorInputMapper() {}
 
-uint32_t CursorInputMapper::getSources() {
+uint32_t CursorInputMapper::getSources() const {
     return mSource;
 }
 
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index 9a8ca01..c84c6c4 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -56,7 +56,7 @@
     explicit CursorInputMapper(InputDeviceContext& deviceContext);
     virtual ~CursorInputMapper();
 
-    virtual uint32_t getSources() override;
+    virtual uint32_t getSources() const override;
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
     virtual void dump(std::string& dump) override;
     virtual void configure(nsecs_t when, const InputReaderConfiguration* config,
diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
index 37d1d74..6b5d37f 100644
--- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
@@ -26,7 +26,7 @@
 ExternalStylusInputMapper::ExternalStylusInputMapper(InputDeviceContext& deviceContext)
       : InputMapper(deviceContext) {}
 
-uint32_t ExternalStylusInputMapper::getSources() {
+uint32_t ExternalStylusInputMapper::getSources() const {
     return AINPUT_SOURCE_STYLUS;
 }
 
diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
index 1d42b30..516aa51 100644
--- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
+++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
@@ -30,7 +30,7 @@
     explicit ExternalStylusInputMapper(InputDeviceContext& deviceContext);
     virtual ~ExternalStylusInputMapper() = default;
 
-    virtual uint32_t getSources() override;
+    virtual uint32_t getSources() const override;
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
     virtual void dump(std::string& dump) override;
     virtual void configure(nsecs_t when, const InputReaderConfiguration* config,
diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp
index b9aef54..7b185e0 100644
--- a/services/inputflinger/reader/mapper/InputMapper.cpp
+++ b/services/inputflinger/reader/mapper/InputMapper.cpp
@@ -51,6 +51,10 @@
     return AKEY_STATE_UNKNOWN;
 }
 
+int32_t InputMapper::getKeyCodeForKeyLocation(int32_t locationKeyCode) const {
+    return AKEYCODE_UNKNOWN;
+}
+
 bool InputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
                                         const int32_t* keyCodes, uint8_t* outFlags) {
     return false;
diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h
index d83d74d..fce6409 100644
--- a/services/inputflinger/reader/mapper/InputMapper.h
+++ b/services/inputflinger/reader/mapper/InputMapper.h
@@ -45,12 +45,13 @@
 
     inline int32_t getDeviceId() { return mDeviceContext.getId(); }
     inline InputDeviceContext& getDeviceContext() { return mDeviceContext; }
+    inline InputDeviceContext& getDeviceContext() const { return mDeviceContext; };
     inline const std::string getDeviceName() const { return mDeviceContext.getName(); }
     inline InputReaderContext* getContext() { return mDeviceContext.getContext(); }
     inline InputReaderPolicyInterface* getPolicy() { return getContext()->getPolicy(); }
     inline InputListenerInterface& getListener() { return getContext()->getListener(); }
 
-    virtual uint32_t getSources() = 0;
+    virtual uint32_t getSources() const = 0;
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
     virtual void dump(std::string& dump);
     virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
@@ -61,6 +62,8 @@
     virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
     virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
     virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+    virtual int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const;
+
     virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
                                        const int32_t* keyCodes, uint8_t* outFlags);
     virtual void vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token);
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
index ad11b05..a8e9bae 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
@@ -25,7 +25,7 @@
 
 JoystickInputMapper::~JoystickInputMapper() {}
 
-uint32_t JoystickInputMapper::getSources() {
+uint32_t JoystickInputMapper::getSources() const {
     return AINPUT_SOURCE_JOYSTICK;
 }
 
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.h b/services/inputflinger/reader/mapper/JoystickInputMapper.h
index bba95ad..307bf5b 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.h
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.h
@@ -26,7 +26,7 @@
     explicit JoystickInputMapper(InputDeviceContext& deviceContext);
     virtual ~JoystickInputMapper();
 
-    virtual uint32_t getSources() override;
+    virtual uint32_t getSources() const override;
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
     virtual void dump(std::string& dump) override;
     virtual void configure(nsecs_t when, const InputReaderConfiguration* config,
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index a8602a4..1d63c0e 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -95,7 +95,7 @@
 
 KeyboardInputMapper::~KeyboardInputMapper() {}
 
-uint32_t KeyboardInputMapper::getSources() {
+uint32_t KeyboardInputMapper::getSources() const {
     return mSource;
 }
 
@@ -375,6 +375,10 @@
     return getDeviceContext().getScanCodeState(scanCode);
 }
 
+int32_t KeyboardInputMapper::getKeyCodeForKeyLocation(int32_t locationKeyCode) const {
+    return getDeviceContext().getKeyCodeForKeyLocation(locationKeyCode);
+}
+
 bool KeyboardInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
                                                 const int32_t* keyCodes, uint8_t* outFlags) {
     return getDeviceContext().markSupportedKeyCodes(numCodes, keyCodes, outFlags);
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index fc92320..3787696 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -26,7 +26,7 @@
     KeyboardInputMapper(InputDeviceContext& deviceContext, uint32_t source, int32_t keyboardType);
     virtual ~KeyboardInputMapper();
 
-    virtual uint32_t getSources() override;
+    virtual uint32_t getSources() const override;
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
     virtual void dump(std::string& dump) override;
     virtual void configure(nsecs_t when, const InputReaderConfiguration* config,
@@ -38,6 +38,7 @@
     virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override;
     virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
                                        const int32_t* keyCodes, uint8_t* outFlags) override;
+    virtual int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const override;
 
     virtual int32_t getMetaState() override;
     virtual bool updateMetaState(int32_t keyCode) override;
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index 4bd1cd8..ff3a592 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -282,7 +282,7 @@
 
         if (outCount >= MAX_POINTERS) {
             if (DEBUG_POINTERS) {
-                ALOGD("MultiTouch device %s emitted more than maximum of %d pointers; "
+                ALOGD("MultiTouch device %s emitted more than maximum of %zu pointers; "
                       "ignoring the rest.",
                       getDeviceName().c_str(), MAX_POINTERS);
             }
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
index b83a8fc..eca25f6 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
@@ -31,7 +31,7 @@
 
 RotaryEncoderInputMapper::~RotaryEncoderInputMapper() {}
 
-uint32_t RotaryEncoderInputMapper::getSources() {
+uint32_t RotaryEncoderInputMapper::getSources() const {
     return mSource;
 }
 
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
index e0c9404..1859355 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
@@ -27,7 +27,7 @@
     explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext);
     virtual ~RotaryEncoderInputMapper();
 
-    virtual uint32_t getSources() override;
+    virtual uint32_t getSources() const override;
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
     virtual void dump(std::string& dump) override;
     virtual void configure(nsecs_t when, const InputReaderConfiguration* config,
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
index 677a372..b01c2bc 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
@@ -57,7 +57,7 @@
 
 SensorInputMapper::~SensorInputMapper() {}
 
-uint32_t SensorInputMapper::getSources() {
+uint32_t SensorInputMapper::getSources() const {
     return AINPUT_SOURCE_SENSOR;
 }
 
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h
index 1797fe3..27a6177 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.h
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.h
@@ -28,7 +28,7 @@
     explicit SensorInputMapper(InputDeviceContext& deviceContext);
     ~SensorInputMapper() override;
 
-    uint32_t getSources() override;
+    uint32_t getSources() const override;
     void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
     void dump(std::string& dump) override;
     void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) override;
diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
index 3237824..ebb5de6 100644
--- a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
@@ -25,7 +25,7 @@
 
 SwitchInputMapper::~SwitchInputMapper() {}
 
-uint32_t SwitchInputMapper::getSources() {
+uint32_t SwitchInputMapper::getSources() const {
     return AINPUT_SOURCE_SWITCH;
 }
 
diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.h b/services/inputflinger/reader/mapper/SwitchInputMapper.h
index 4d74163..64b9aa2 100644
--- a/services/inputflinger/reader/mapper/SwitchInputMapper.h
+++ b/services/inputflinger/reader/mapper/SwitchInputMapper.h
@@ -26,7 +26,7 @@
     explicit SwitchInputMapper(InputDeviceContext& deviceContext);
     virtual ~SwitchInputMapper();
 
-    virtual uint32_t getSources() override;
+    virtual uint32_t getSources() const override;
     virtual void process(const RawEvent* rawEvent) override;
 
     virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode) override;
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 0a5de27..f729ba9 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -178,7 +178,7 @@
 
 TouchInputMapper::~TouchInputMapper() {}
 
-uint32_t TouchInputMapper::getSources() {
+uint32_t TouchInputMapper::getSources() const {
     return mSource;
 }
 
@@ -565,6 +565,12 @@
             return getDeviceContext().getAssociatedViewport();
         }
 
+        const std::optional<std::string> associatedDisplayUniqueId =
+                getDeviceContext().getAssociatedDisplayUniqueId();
+        if (associatedDisplayUniqueId) {
+            return getDeviceContext().getAssociatedViewport();
+        }
+
         if (mDeviceMode == DeviceMode::POINTER) {
             std::optional<DisplayViewport> viewport =
                     mConfig.getDisplayViewportById(mConfig.defaultPointerDisplayId);
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 9fd30e4..c948f56 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -138,7 +138,7 @@
     explicit TouchInputMapper(InputDeviceContext& deviceContext);
     ~TouchInputMapper() override;
 
-    uint32_t getSources() override;
+    uint32_t getSources() const override;
     void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
     void dump(std::string& dump) override;
     void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) override;
diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
index 1976fed..33db527 100644
--- a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
@@ -25,7 +25,7 @@
 
 VibratorInputMapper::~VibratorInputMapper() {}
 
-uint32_t VibratorInputMapper::getSources() {
+uint32_t VibratorInputMapper::getSources() const {
     return 0;
 }
 
diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.h b/services/inputflinger/reader/mapper/VibratorInputMapper.h
index 7ce621a..d3c22b6 100644
--- a/services/inputflinger/reader/mapper/VibratorInputMapper.h
+++ b/services/inputflinger/reader/mapper/VibratorInputMapper.h
@@ -26,7 +26,7 @@
     explicit VibratorInputMapper(InputDeviceContext& deviceContext);
     virtual ~VibratorInputMapper();
 
-    virtual uint32_t getSources() override;
+    virtual uint32_t getSources() const override;
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
     virtual void process(const RawEvent* rawEvent) override;
 
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index e686924..d19dbaf 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -57,7 +57,8 @@
         ],
     },
     static_libs: [
-        "libc++fs"
+        "libc++fs",
+        "libinput",
     ],
     require_root: true,
     test_suites: ["device-tests"],
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 0814bc2..aa2f832 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -558,7 +558,7 @@
     MotionEvent event;
     PointerProperties pointerProperties[MAX_POINTERS + 1];
     PointerCoords pointerCoords[MAX_POINTERS + 1];
-    for (int i = 0; i <= MAX_POINTERS; i++) {
+    for (size_t i = 0; i <= MAX_POINTERS; i++) {
         pointerProperties[i].clear();
         pointerProperties[i].id = i;
         pointerCoords[i].clear();
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 2c5b321..54cf15d 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -439,6 +439,8 @@
         KeyedVector<int32_t, KeyInfo> keysByScanCode;
         KeyedVector<int32_t, KeyInfo> keysByUsageCode;
         KeyedVector<int32_t, bool> leds;
+        // fake mapping which would normally come from keyCharacterMap
+        std::unordered_map<int32_t, int32_t> keyCodeMapping;
         std::unordered_map<int32_t, SensorInfo> sensorsByAbsCode;
         BitArray<MSC_MAX> mscBitmask;
         std::vector<VirtualKeyDefinition> virtualKeys;
@@ -600,6 +602,11 @@
         }
     }
 
+    void addKeyCodeMapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) {
+        Device* device = getDevice(deviceId);
+        device->keyCodeMapping.insert_or_assign(fromKeyCode, toKeyCode);
+    }
+
     void addLed(int32_t deviceId, int32_t led, bool initialState) {
         Device* device = getDevice(deviceId);
         device->leds.add(led, initialState);
@@ -863,6 +870,15 @@
         return -1;
     }
 
+    int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override {
+        Device* device = getDevice(deviceId);
+        if (!device) {
+            return AKEYCODE_UNKNOWN;
+        }
+        auto it = device->keyCodeMapping.find(locationKeyCode);
+        return it != device->keyCodeMapping.end() ? it->second : locationKeyCode;
+    }
+
     // Return true if the device has non-empty key layout.
     bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
                                uint8_t* outFlags) const override {
@@ -1034,6 +1050,8 @@
     KeyedVector<int32_t, int32_t> mKeyCodeStates;
     KeyedVector<int32_t, int32_t> mScanCodeStates;
     KeyedVector<int32_t, int32_t> mSwitchStates;
+    // fake mapping which would normally come from keyCharacterMap
+    std::unordered_map<int32_t, int32_t> mKeyCodeMapping;
     std::vector<int32_t> mSupportedKeyCodes;
 
     std::mutex mLock;
@@ -1122,8 +1140,12 @@
         mSupportedKeyCodes.push_back(keyCode);
     }
 
+    void addKeyCodeMapping(int32_t fromKeyCode, int32_t toKeyCode) {
+        mKeyCodeMapping.insert_or_assign(fromKeyCode, toKeyCode);
+    }
+
 private:
-    uint32_t getSources() override { return mSources; }
+    uint32_t getSources() const override { return mSources; }
 
     void populateDeviceInfo(InputDeviceInfo* deviceInfo) override {
         InputMapper::populateDeviceInfo(deviceInfo);
@@ -1164,6 +1186,11 @@
         return index >= 0 ? mKeyCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN;
     }
 
+    int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const override {
+        auto it = mKeyCodeMapping.find(locationKeyCode);
+        return it != mKeyCodeMapping.end() ? it->second : locationKeyCode;
+    }
+
     int32_t getScanCodeState(uint32_t, int32_t scanCode) override {
         ssize_t index = mScanCodeStates.indexOfKey(scanCode);
         return index >= 0 ? mScanCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN;
@@ -1712,6 +1739,37 @@
             << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
 }
 
+TEST_F(InputReaderTest, GetKeyCodeForKeyLocation_ForwardsRequestsToMappers) {
+    constexpr int32_t deviceId = END_RESERVED_ID + 1000;
+    constexpr int32_t eventHubId = 1;
+    FakeInputMapper& mapper = addDeviceWithFakeInputMapper(deviceId, eventHubId, "keyboard",
+                                                           InputDeviceClass::KEYBOARD,
+                                                           AINPUT_SOURCE_KEYBOARD, nullptr);
+    mapper.addKeyCodeMapping(AKEYCODE_Y, AKEYCODE_Z);
+
+    ASSERT_EQ(AKEYCODE_UNKNOWN, mReader->getKeyCodeForKeyLocation(0, AKEYCODE_Y))
+            << "Should return unknown when the device with the specified id is not found.";
+
+    ASSERT_EQ(AKEYCODE_Z, mReader->getKeyCodeForKeyLocation(deviceId, AKEYCODE_Y))
+            << "Should return correct mapping when device id is valid and mapping exists.";
+
+    ASSERT_EQ(AKEYCODE_A, mReader->getKeyCodeForKeyLocation(deviceId, AKEYCODE_A))
+            << "Should return the location key code when device id is valid and there's no "
+               "mapping.";
+}
+
+TEST_F(InputReaderTest, GetKeyCodeForKeyLocation_NoKeyboardMapper) {
+    constexpr int32_t deviceId = END_RESERVED_ID + 1000;
+    constexpr int32_t eventHubId = 1;
+    FakeInputMapper& mapper = addDeviceWithFakeInputMapper(deviceId, eventHubId, "joystick",
+                                                           InputDeviceClass::JOYSTICK,
+                                                           AINPUT_SOURCE_GAMEPAD, nullptr);
+    mapper.addKeyCodeMapping(AKEYCODE_Y, AKEYCODE_Z);
+
+    ASSERT_EQ(AKEYCODE_UNKNOWN, mReader->getKeyCodeForKeyLocation(deviceId, AKEYCODE_Y))
+            << "Should return unknown when the device id is valid but there is no keyboard mapper";
+}
+
 TEST_F(InputReaderTest, GetScanCodeState_ForwardsRequestsToMappers) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
     constexpr Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
@@ -2804,7 +2862,7 @@
     // Device should be disabled because it is associated with a specific display, but the
     // corresponding display is not found.
     const std::string DISPLAY_UNIQUE_ID = "displayUniqueId";
-    mFakePolicy->addInputUniqueIdAssociation(DEVICE_NAME, DISPLAY_UNIQUE_ID);
+    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID);
     mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                        InputReaderConfiguration::CHANGE_DISPLAY_INFO);
     ASSERT_FALSE(mDevice->isEnabled());
@@ -2829,6 +2887,21 @@
     ASSERT_FALSE(mDevice->isEnabled());
 }
 
+TEST_F(InputDeviceTest, Configure_UniqueId_CorrectlyMatches) {
+    mFakePolicy->clearViewports();
+    mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD);
+    mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0);
+
+    const std::string DISPLAY_UNIQUE_ID = "displayUniqueId";
+    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID);
+    mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+                                    DISPLAY_ORIENTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID,
+                                    NO_PORT, ViewportType::INTERNAL);
+    mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                       InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueId());
+}
+
 // --- InputMapperTest ---
 
 class InputMapperTest : public testing::Test {
@@ -3609,6 +3682,19 @@
     ASSERT_EQ(0, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
 }
 
+TEST_F(KeyboardInputMapperTest, GetKeyCodeForKeyLocation) {
+    KeyboardInputMapper& mapper =
+            addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+
+    mFakeEventHub->addKeyCodeMapping(EVENTHUB_ID, AKEYCODE_Y, AKEYCODE_Z);
+    ASSERT_EQ(AKEYCODE_Z, mapper.getKeyCodeForKeyLocation(AKEYCODE_Y))
+            << "If a mapping is available, the result is equal to the mapping";
+
+    ASSERT_EQ(AKEYCODE_A, mapper.getKeyCodeForKeyLocation(AKEYCODE_A))
+            << "If no mapping is available, the result is the key location";
+}
+
 TEST_F(KeyboardInputMapperTest, GetScanCodeState) {
     KeyboardInputMapper& mapper =
             addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
diff --git a/services/sensorservice/AidlSensorHalWrapper.cpp b/services/sensorservice/AidlSensorHalWrapper.cpp
index 049d06a..cdd95ca 100644
--- a/services/sensorservice/AidlSensorHalWrapper.cpp
+++ b/services/sensorservice/AidlSensorHalWrapper.cpp
@@ -238,6 +238,18 @@
             break;
         }
 
+        case SensorType::HEAD_TRACKER: {
+            const auto &ht = src.payload.get<Event::EventPayload::headTracker>();
+            dst->head_tracker.rx = ht.rx;
+            dst->head_tracker.ry = ht.ry;
+            dst->head_tracker.rz = ht.rz;
+            dst->head_tracker.vx = ht.vx;
+            dst->head_tracker.vy = ht.vy;
+            dst->head_tracker.vz = ht.vz;
+            dst->head_tracker.discontinuity_count = ht.discontinuityCount;
+            break;
+        }
+
         default: {
             CHECK_GE((int32_t)src.sensorType, (int32_t)SensorType::DEVICE_PRIVATE_BASE);
 
@@ -383,6 +395,20 @@
             break;
         }
 
+        case SensorType::HEAD_TRACKER: {
+            Event::EventPayload::HeadTracker headTracker;
+            headTracker.rx = src.head_tracker.rx;
+            headTracker.ry = src.head_tracker.ry;
+            headTracker.rz = src.head_tracker.rz;
+            headTracker.vx = src.head_tracker.vx;
+            headTracker.vy = src.head_tracker.vy;
+            headTracker.vz = src.head_tracker.vz;
+            headTracker.discontinuityCount = src.head_tracker.discontinuity_count;
+
+            dst->payload.set<Event::EventPayload::Tag::headTracker>(headTracker);
+            break;
+        }
+
         default: {
             CHECK_GE((int32_t)dst->sensorType, (int32_t)SensorType::DEVICE_PRIVATE_BASE);
 
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 3a1dc7c..af0f524 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -155,9 +155,7 @@
         "DisplayHardware/AidlComposerHal.cpp",
         "DisplayHardware/HidlComposerHal.cpp",
         "DisplayHardware/ComposerHal.cpp",
-        "DisplayHardware/DisplayIdentification.cpp",
         "DisplayHardware/FramebufferSurface.cpp",
-        "DisplayHardware/Hash.cpp",
         "DisplayHardware/HWC2.cpp",
         "DisplayHardware/HWComposer.cpp",
         "DisplayHardware/PowerAdvisor.cpp",
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index 0c93872..40fc342 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -316,8 +316,7 @@
     return assignTransform(&mDrawingState.transform, t);
 }
 
-bool BufferStateLayer::setMatrix(const layer_state_t::matrix22_t& matrix,
-                                 bool allowNonRectPreservingTransforms) {
+bool BufferStateLayer::setMatrix(const layer_state_t::matrix22_t& matrix) {
     if (mRequestedTransform.dsdx() == matrix.dsdx && mRequestedTransform.dtdy() == matrix.dtdy &&
         mRequestedTransform.dtdx() == matrix.dtdx && mRequestedTransform.dsdy() == matrix.dsdy) {
         return false;
@@ -326,12 +325,6 @@
     ui::Transform t;
     t.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy);
 
-    if (!allowNonRectPreservingTransforms && !t.preserveRects()) {
-        ALOGW("Attempt to set rotation matrix without permission ACCESS_SURFACE_FLINGER nor "
-              "ROTATE_SURFACE_FLINGER ignored");
-        return false;
-    }
-
     mRequestedTransform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy);
 
     mDrawingState.sequence++;
diff --git a/services/surfaceflinger/BufferStateLayer.h b/services/surfaceflinger/BufferStateLayer.h
index 2f613d7..248e013 100644
--- a/services/surfaceflinger/BufferStateLayer.h
+++ b/services/surfaceflinger/BufferStateLayer.h
@@ -70,8 +70,7 @@
     bool addFrameEvent(const sp<Fence>& acquireFence, nsecs_t postedTime,
                        nsecs_t requestedPresentTime) override;
     bool setPosition(float /*x*/, float /*y*/) override;
-    bool setMatrix(const layer_state_t::matrix22_t& /*matrix*/,
-                   bool /*allowNonRectPreservingTransforms*/);
+    bool setMatrix(const layer_state_t::matrix22_t& /*matrix*/);
 
     // Override to ignore legacy layer state properties that are not used by BufferStateLayer
     bool setSize(uint32_t /*w*/, uint32_t /*h*/) override { return false; }
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
index a43cc63..47aacc9 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
@@ -19,7 +19,7 @@
 #include <cstdint>
 #include <optional>
 
-#include "DisplayHardware/DisplayIdentification.h"
+#include <ui/DisplayIdentification.h>
 
 #include <compositionengine/Output.h>
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index 6cb12dd..d8644a4 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -34,7 +34,7 @@
 #include <utils/StrongPointer.h>
 #include <utils/Vector.h>
 
-#include "DisplayHardware/DisplayIdentification.h"
+#include <ui/DisplayIdentification.h>
 
 namespace android {
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
index a2824f5..bf5184e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
@@ -28,8 +28,8 @@
 #pragma clang diagnostic ignored "-Wconversion"
 #pragma clang diagnostic ignored "-Wextra"
 
+#include <ui/DisplayIdentification.h>
 #include "DisplayHardware/ComposerHal.h"
-#include "DisplayHardware/DisplayIdentification.h"
 
 #include "LayerFE.h"
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
index 9bfd78f..ebe112b 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -26,7 +26,8 @@
 #include <ui/PixelFormat.h>
 #include <ui/Size.h>
 
-#include "DisplayHardware/DisplayIdentification.h"
+#include <ui/DisplayIdentification.h>
+
 #include "DisplayHardware/HWComposer.h"
 #include "DisplayHardware/PowerAdvisor.h"
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
index 0082dac..d64d676 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
@@ -25,7 +25,7 @@
 #include <ui/FloatRect.h>
 #include <ui/Rect.h>
 
-#include "DisplayHardware/DisplayIdentification.h"
+#include <ui/DisplayIdentification.h>
 
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
index f71f0e4..84afb59 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
@@ -22,8 +22,7 @@
 #include <compositionengine/mock/Output.h>
 #include <gmock/gmock.h>
 #include <system/window.h>
-
-#include "DisplayHardware/DisplayIdentification.h"
+#include <ui/DisplayIdentification.h>
 
 namespace android::compositionengine::mock {
 
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 192ee04..162d84e 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -1059,9 +1059,11 @@
 
     // If we have a valid current display brightness use that, otherwise fall back to the
     // display's max desired
-    clientCompositionDisplay.maxLuminance = outputState.displayBrightnessNits > 0.f
+    clientCompositionDisplay.currentLuminanceNits = outputState.displayBrightnessNits > 0.f
             ? outputState.displayBrightnessNits
             : mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance();
+    clientCompositionDisplay.maxLuminance =
+            mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance();
     clientCompositionDisplay.targetLuminanceNits = outputState.clientTargetWhitePointNits;
 
     // Compute the global color transform matrix.
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index 0918510..8d26747 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -401,6 +401,19 @@
     return true;
 }
 
+namespace {
+bool isDisplayDecoration(const CachedSet& cachedSet) {
+    return cachedSet.getLayerCount() == 1 &&
+            cachedSet.getFirstLayer()
+                    .getState()
+                    ->getOutputLayer()
+                    ->getLayerFE()
+                    .getCompositionState()
+                    ->compositionType ==
+            aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION;
+}
+} // namespace
+
 std::vector<Flattener::Run> Flattener::findCandidateRuns(time_point now) const {
     ATRACE_CALL();
     std::vector<Run> runs;
@@ -424,7 +437,7 @@
         }
 
         if (layerIsInactive && (firstLayer || runHasFirstLayer || !layerHasBlur) &&
-            !currentSet->hasUnsupportedDataspace()) {
+            !currentSet->hasUnsupportedDataspace() && !isDisplayDecoration(*currentSet)) {
             if (isPartOfRun) {
                 builder.increment();
             } else {
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 394fe03..bcd4dc2 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -39,7 +39,7 @@
     HWComposer();
     ~HWComposer() override;
 
-    MOCK_METHOD1(setCallback, void(HWC2::ComposerCallback*));
+    MOCK_METHOD1(setCallback, void(HWC2::ComposerCallback&));
     MOCK_CONST_METHOD3(getDisplayIdentificationData,
                        bool(hal::HWDisplayId, uint8_t*, DisplayIdentificationData*));
     MOCK_CONST_METHOD1(hasCapability, bool(hal::Capability));
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index b13e13c..655db4b 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -3072,6 +3072,8 @@
     static constexpr float kDefaultMaxLuminance = 0.9f;
     static constexpr float kDefaultAvgLuminance = 0.7f;
     static constexpr float kDefaultMinLuminance = 0.1f;
+    static constexpr float kUnknownLuminance = -1.f;
+    static constexpr float kDisplayLuminance = 80.f;
     static constexpr float kClientTargetLuminanceNits = 200.f;
 
     static const Rect kDefaultOutputFrame;
@@ -3105,6 +3107,7 @@
 const mat4 OutputComposeSurfacesTest::kDefaultColorTransformMat{mat4() * 0.5f};
 const compositionengine::CompositionRefreshArgs OutputComposeSurfacesTest::kDefaultRefreshArgs;
 const Region OutputComposeSurfacesTest::kDebugRegion{Rect{100, 101, 102, 103}};
+
 const HdrCapabilities OutputComposeSurfacesTest::
         kHdrCapabilities{{},
                          OutputComposeSurfacesTest::kDefaultMaxLuminance,
@@ -3408,6 +3411,14 @@
         auto andIfUsesHdr(bool used) {
             EXPECT_CALL(*getInstance()->mDisplayColorProfile, hasWideColorGamut())
                     .WillOnce(Return(used));
+            return nextState<OutputWithDisplayBrightnessNits>();
+        }
+    };
+
+    struct OutputWithDisplayBrightnessNits
+          : public CallOrderStateMachineHelper<TestType, OutputWithDisplayBrightnessNits> {
+        auto withDisplayBrightnessNits(float nits) {
+            getInstance()->mOutput.mState.displayBrightnessNits = nits;
             return nextState<SkipColorTransformState>();
         }
     };
@@ -3439,11 +3450,34 @@
 TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrMixedComposition) {
     verify().ifMixedCompositionIs(true)
             .andIfUsesHdr(true)
+            .withDisplayBrightnessNits(kUnknownLuminance)
             .andIfSkipColorTransform(false)
-            .thenExpectDisplaySettingsUsed({kDefaultOutputDestinationClip, kDefaultOutputViewport,
-                                            kDefaultMaxLuminance, kDefaultOutputDataspace, mat4(),
-                                            kDefaultOutputOrientationFlags,
-                                            kClientTargetLuminanceNits})
+            .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip,
+                                            .clip = kDefaultOutputViewport,
+                                            .maxLuminance = kDefaultMaxLuminance,
+                                            .currentLuminanceNits = kDefaultMaxLuminance,
+                                            .outputDataspace = kDefaultOutputDataspace,
+                                            .colorTransform = mat4(),
+                                            .orientation = kDefaultOutputOrientationFlags,
+                                            .targetLuminanceNits = kClientTargetLuminanceNits})
+            .execute()
+            .expectAFenceWasReturned();
+}
+
+TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings,
+       forHdrMixedCompositionWithDisplayBrightness) {
+    verify().ifMixedCompositionIs(true)
+            .andIfUsesHdr(true)
+            .withDisplayBrightnessNits(kDisplayLuminance)
+            .andIfSkipColorTransform(false)
+            .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip,
+                                            .clip = kDefaultOutputViewport,
+                                            .maxLuminance = kDefaultMaxLuminance,
+                                            .currentLuminanceNits = kDisplayLuminance,
+                                            .outputDataspace = kDefaultOutputDataspace,
+                                            .colorTransform = mat4(),
+                                            .orientation = kDefaultOutputOrientationFlags,
+                                            .targetLuminanceNits = kClientTargetLuminanceNits})
             .execute()
             .expectAFenceWasReturned();
 }
@@ -3451,11 +3485,16 @@
 TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrMixedComposition) {
     verify().ifMixedCompositionIs(true)
             .andIfUsesHdr(false)
+            .withDisplayBrightnessNits(kUnknownLuminance)
             .andIfSkipColorTransform(false)
-            .thenExpectDisplaySettingsUsed({kDefaultOutputDestinationClip, kDefaultOutputViewport,
-                                            kDefaultMaxLuminance, kDefaultOutputDataspace, mat4(),
-                                            kDefaultOutputOrientationFlags,
-                                            kClientTargetLuminanceNits})
+            .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip,
+                                            .clip = kDefaultOutputViewport,
+                                            .maxLuminance = kDefaultMaxLuminance,
+                                            .currentLuminanceNits = kDefaultMaxLuminance,
+                                            .outputDataspace = kDefaultOutputDataspace,
+                                            .colorTransform = mat4(),
+                                            .orientation = kDefaultOutputOrientationFlags,
+                                            .targetLuminanceNits = kClientTargetLuminanceNits})
             .execute()
             .expectAFenceWasReturned();
 }
@@ -3463,11 +3502,16 @@
 TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrOnlyClientComposition) {
     verify().ifMixedCompositionIs(false)
             .andIfUsesHdr(true)
+            .withDisplayBrightnessNits(kUnknownLuminance)
             .andIfSkipColorTransform(false)
-            .thenExpectDisplaySettingsUsed(
-                    {kDefaultOutputDestinationClip, kDefaultOutputViewport, kDefaultMaxLuminance,
-                     kDefaultOutputDataspace, kDefaultColorTransformMat,
-                     kDefaultOutputOrientationFlags, kClientTargetLuminanceNits})
+            .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip,
+                                            .clip = kDefaultOutputViewport,
+                                            .maxLuminance = kDefaultMaxLuminance,
+                                            .currentLuminanceNits = kDefaultMaxLuminance,
+                                            .outputDataspace = kDefaultOutputDataspace,
+                                            .colorTransform = kDefaultColorTransformMat,
+                                            .orientation = kDefaultOutputOrientationFlags,
+                                            .targetLuminanceNits = kClientTargetLuminanceNits})
             .execute()
             .expectAFenceWasReturned();
 }
@@ -3475,11 +3519,16 @@
 TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrOnlyClientComposition) {
     verify().ifMixedCompositionIs(false)
             .andIfUsesHdr(false)
+            .withDisplayBrightnessNits(kUnknownLuminance)
             .andIfSkipColorTransform(false)
-            .thenExpectDisplaySettingsUsed(
-                    {kDefaultOutputDestinationClip, kDefaultOutputViewport, kDefaultMaxLuminance,
-                     kDefaultOutputDataspace, kDefaultColorTransformMat,
-                     kDefaultOutputOrientationFlags, kClientTargetLuminanceNits})
+            .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip,
+                                            .clip = kDefaultOutputViewport,
+                                            .maxLuminance = kDefaultMaxLuminance,
+                                            .currentLuminanceNits = kDefaultMaxLuminance,
+                                            .outputDataspace = kDefaultOutputDataspace,
+                                            .colorTransform = kDefaultColorTransformMat,
+                                            .orientation = kDefaultOutputOrientationFlags,
+                                            .targetLuminanceNits = kClientTargetLuminanceNits})
             .execute()
             .expectAFenceWasReturned();
 }
@@ -3488,11 +3537,16 @@
        usesExpectedDisplaySettingsForHdrOnlyClientCompositionWithSkipClientTransform) {
     verify().ifMixedCompositionIs(false)
             .andIfUsesHdr(true)
+            .withDisplayBrightnessNits(kUnknownLuminance)
             .andIfSkipColorTransform(true)
-            .thenExpectDisplaySettingsUsed({kDefaultOutputDestinationClip, kDefaultOutputViewport,
-                                            kDefaultMaxLuminance, kDefaultOutputDataspace, mat4(),
-                                            kDefaultOutputOrientationFlags,
-                                            kClientTargetLuminanceNits})
+            .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip,
+                                            .clip = kDefaultOutputViewport,
+                                            .maxLuminance = kDefaultMaxLuminance,
+                                            .currentLuminanceNits = kDefaultMaxLuminance,
+                                            .outputDataspace = kDefaultOutputDataspace,
+                                            .colorTransform = mat4(),
+                                            .orientation = kDefaultOutputOrientationFlags,
+                                            .targetLuminanceNits = kClientTargetLuminanceNits})
             .execute()
             .expectAFenceWasReturned();
 }
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index 58dc244..656ef9a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -1337,5 +1337,56 @@
     EXPECT_NE(nullptr, overrideBuffer4);
 }
 
+TEST_F(FlattenerTest, flattenLayers_skips_DISPLAY_DECORATION) {
+    auto& layerState1 = mTestLayers[0]->layerState;
+    const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
+
+    auto& layerState2 = mTestLayers[1]->layerState;
+    const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer;
+
+    // The third layer uses DISPLAY_DECORATION, which should never be cached.
+    auto& layerState3 = mTestLayers[2]->layerState;
+    const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer;
+    mTestLayers[2]->layerFECompositionState.compositionType =
+            aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION;
+    mTestLayers[2]->layerState->update(&mTestLayers[2]->outputLayer);
+
+    const std::vector<const LayerState*> layers = {
+            layerState1.get(),
+            layerState2.get(),
+            layerState3.get(),
+    };
+
+    initializeFlattener(layers);
+
+    mTime += 200ms;
+    initializeOverrideBuffer(layers);
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+
+    // This will render a CachedSet.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+
+    // We've rendered a CachedSet, but we haven't merged it in.
+    EXPECT_EQ(nullptr, overrideBuffer1);
+    EXPECT_EQ(nullptr, overrideBuffer2);
+    EXPECT_EQ(nullptr, overrideBuffer3);
+
+    // This time we merge the CachedSet in, so we have a new hash, and we should
+    // only have two sets.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+    initializeOverrideBuffer(layers);
+    EXPECT_NE(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+
+    EXPECT_NE(nullptr, overrideBuffer1);
+    EXPECT_EQ(overrideBuffer1, overrideBuffer2);
+    EXPECT_EQ(nullptr, overrideBuffer3);
+}
+
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index ad44b16..3cae30f 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -41,7 +41,7 @@
 
 #include "MainThreadGuard.h"
 
-#include "DisplayHardware/DisplayIdentification.h"
+#include <ui/DisplayIdentification.h>
 #include "DisplayHardware/DisplayMode.h"
 #include "DisplayHardware/Hal.h"
 #include "DisplayHardware/PowerAdvisor.h"
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 1448e56..b1057c3 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -30,6 +30,8 @@
 #include <algorithm>
 #include <cinttypes>
 
+#include "HWC2.h"
+
 namespace android {
 
 using hardware::hidl_handle;
@@ -169,40 +171,47 @@
 
 class AidlIComposerCallbackWrapper : public BnComposerCallback {
 public:
-    AidlIComposerCallbackWrapper(sp<V2_4::IComposerCallback> callback)
-          : mCallback(std::move(callback)) {}
+    AidlIComposerCallbackWrapper(HWC2::ComposerCallback& callback) : mCallback(callback) {}
 
     ::ndk::ScopedAStatus onHotplug(int64_t in_display, bool in_connected) override {
         const auto connection = in_connected ? V2_4::IComposerCallback::Connection::CONNECTED
                                              : V2_4::IComposerCallback::Connection::DISCONNECTED;
-        mCallback->onHotplug(translate<Display>(in_display), connection);
+        mCallback.onComposerHalHotplug(translate<Display>(in_display), connection);
         return ::ndk::ScopedAStatus::ok();
     }
 
     ::ndk::ScopedAStatus onRefresh(int64_t in_display) override {
-        mCallback->onRefresh(translate<Display>(in_display));
+        mCallback.onComposerHalRefresh(translate<Display>(in_display));
         return ::ndk::ScopedAStatus::ok();
     }
+
     ::ndk::ScopedAStatus onSeamlessPossible(int64_t in_display) override {
-        mCallback->onSeamlessPossible(translate<Display>(in_display));
+        mCallback.onComposerHalSeamlessPossible(translate<Display>(in_display));
         return ::ndk::ScopedAStatus::ok();
     }
+
     ::ndk::ScopedAStatus onVsync(int64_t in_display, int64_t in_timestamp,
                                  int32_t in_vsyncPeriodNanos) override {
-        mCallback->onVsync_2_4(translate<Display>(in_display), in_timestamp,
-                               static_cast<uint32_t>(in_vsyncPeriodNanos));
+        mCallback.onComposerHalVsync(translate<Display>(in_display), in_timestamp,
+                                     static_cast<uint32_t>(in_vsyncPeriodNanos));
         return ::ndk::ScopedAStatus::ok();
     }
+
     ::ndk::ScopedAStatus onVsyncPeriodTimingChanged(
             int64_t in_display, const AidlVsyncPeriodChangeTimeline& in_updatedTimeline) override {
-        mCallback->onVsyncPeriodTimingChanged(translate<Display>(in_display),
-                                              translate<V2_4::VsyncPeriodChangeTimeline>(
-                                                      in_updatedTimeline));
+        mCallback.onComposerHalVsyncPeriodTimingChanged(translate<Display>(in_display),
+                                                        translate<V2_4::VsyncPeriodChangeTimeline>(
+                                                                in_updatedTimeline));
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    ::ndk::ScopedAStatus onVsyncIdle(int64_t in_display) override {
+        mCallback.onComposerHalVsyncIdle(translate<Display>(in_display));
         return ::ndk::ScopedAStatus::ok();
     }
 
 private:
-    sp<V2_4::IComposerCallback> mCallback;
+    HWC2::ComposerCallback& mCallback;
 };
 
 std::string AidlComposer::instance(const std::string& serviceName) {
@@ -262,10 +271,11 @@
     return info;
 }
 
-void AidlComposer::registerCallback(const sp<IComposerCallback>& callback) {
+void AidlComposer::registerCallback(HWC2::ComposerCallback& callback) {
     if (mAidlComposerCallback) {
         ALOGE("Callback already registered");
     }
+
     mAidlComposerCallback = ndk::SharedRefBase::make<AidlIComposerCallbackWrapper>(callback);
     AIBinder_setMinSchedulerPolicy(mAidlComposerCallback->asBinder().get(), SCHED_FIFO, 2);
 
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index 6770017..374a436 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -63,7 +63,7 @@
     std::vector<IComposer::Capability> getCapabilities() override;
     std::string dumpDebugInfo() override;
 
-    void registerCallback(const sp<IComposerCallback>& callback) override;
+    void registerCallback(HWC2::ComposerCallback& callback) override;
 
     // Reset all pending commands in the command buffer. Useful if you want to
     // skip a frame but have already queued some commands.
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index 22f424f..fe55e6b 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -34,11 +34,17 @@
 #include <aidl/android/hardware/graphics/composer3/Color.h>
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
+#include <aidl/android/hardware/graphics/composer3/IComposerCallback.h>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
 
-namespace android::Hwc2 {
+namespace android {
+namespace HWC2 {
+struct ComposerCallback;
+} // namespace HWC2
+
+namespace Hwc2 {
 
 namespace types = hardware::graphics::common;
 
@@ -46,6 +52,7 @@
 namespace V2_2 = hardware::graphics::composer::V2_2;
 namespace V2_3 = hardware::graphics::composer::V2_3;
 namespace V2_4 = hardware::graphics::composer::V2_4;
+namespace V3_0 = ::aidl::android::hardware::graphics::composer3;
 
 using types::V1_0::ColorTransform;
 using types::V1_0::Transform;
@@ -89,7 +96,7 @@
     virtual std::vector<IComposer::Capability> getCapabilities() = 0;
     virtual std::string dumpDebugInfo() = 0;
 
-    virtual void registerCallback(const sp<IComposerCallback>& callback) = 0;
+    virtual void registerCallback(HWC2::ComposerCallback& callback) = 0;
 
     // Reset all pending commands in the command buffer. Useful if you want to
     // skip a frame but have already queued some commands.
@@ -109,9 +116,8 @@
     virtual Error destroyLayer(Display display, Layer layer) = 0;
 
     virtual Error getActiveConfig(Display display, Config* outConfig) = 0;
-    virtual Error getChangedCompositionTypes(
-            Display display, std::vector<Layer>* outLayers,
-            std::vector<aidl::android::hardware::graphics::composer3::Composition>* outTypes) = 0;
+    virtual Error getChangedCompositionTypes(Display display, std::vector<Layer>* outLayers,
+                                             std::vector<V3_0::Composition>* outTypes) = 0;
     virtual Error getColorModes(Display display, std::vector<ColorMode>* outModes) = 0;
     virtual Error getDisplayAttribute(Display display, Config config,
                                       IComposerClient::Attribute attribute, int32_t* outValue) = 0;
@@ -225,10 +231,8 @@
                                        const DisplayBrightnessOptions& options) = 0;
 
     // Composer HAL 2.4
-    virtual Error getDisplayCapabilities(
-            Display display,
-            std::vector<aidl::android::hardware::graphics::composer3::DisplayCapability>*
-                    outCapabilities) = 0;
+    virtual Error getDisplayCapabilities(Display display,
+                                         std::vector<V3_0::DisplayCapability>* outCapabilities) = 0;
     virtual V2_4::Error getDisplayConnectionType(
             Display display, IComposerClient::DisplayConnectionType* outType) = 0;
     virtual V2_4::Error getDisplayVsyncPeriod(Display display,
@@ -263,4 +267,5 @@
     virtual Error getPreferredBootDisplayConfig(Display displayId, Config*) = 0;
 };
 
-} // namespace android::Hwc2
+} // namespace Hwc2
+} // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
index 3123351..d41a856 100644
--- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
+++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
@@ -26,7 +26,7 @@
 #include <ui/DisplayId.h>
 #include <ui/Size.h>
 
-#include "DisplayIdentification.h"
+#include <ui/DisplayIdentification.h>
 
 // ---------------------------------------------------------------------------
 namespace android {
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index 57eb128..0a605a8 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -72,6 +72,7 @@
     virtual void onComposerHalVsyncPeriodTimingChanged(hal::HWDisplayId,
                                                        const hal::VsyncPeriodChangeTimeline&) = 0;
     virtual void onComposerHalSeamlessPossible(hal::HWDisplayId) = 0;
+    virtual void onComposerHalVsyncIdle(hal::HWDisplayId) = 0;
 
 protected:
     ~ComposerCallback() = default;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 44e4597..9174ec7 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -74,63 +74,6 @@
 namespace hal = android::hardware::graphics::composer::hal;
 
 namespace android {
-namespace {
-
-using android::hardware::Return;
-using android::hardware::Void;
-using android::HWC2::ComposerCallback;
-
-class ComposerCallbackBridge : public hal::IComposerCallback {
-public:
-    ComposerCallbackBridge(ComposerCallback* callback, bool vsyncSwitchingSupported)
-          : mCallback(callback), mVsyncSwitchingSupported(vsyncSwitchingSupported) {}
-
-    Return<void> onHotplug(hal::HWDisplayId display, hal::Connection connection) override {
-        mCallback->onComposerHalHotplug(display, connection);
-        return Void();
-    }
-
-    Return<void> onRefresh(hal::HWDisplayId display) override {
-        mCallback->onComposerHalRefresh(display);
-        return Void();
-    }
-
-    Return<void> onVsync(hal::HWDisplayId display, int64_t timestamp) override {
-        if (!mVsyncSwitchingSupported) {
-            mCallback->onComposerHalVsync(display, timestamp, std::nullopt);
-        } else {
-            ALOGW("Unexpected onVsync callback on composer >= 2.4, ignoring.");
-        }
-        return Void();
-    }
-
-    Return<void> onVsync_2_4(hal::HWDisplayId display, int64_t timestamp,
-                             hal::VsyncPeriodNanos vsyncPeriodNanos) override {
-        if (mVsyncSwitchingSupported) {
-            mCallback->onComposerHalVsync(display, timestamp, vsyncPeriodNanos);
-        } else {
-            ALOGW("Unexpected onVsync_2_4 callback on composer <= 2.3, ignoring.");
-        }
-        return Void();
-    }
-
-    Return<void> onVsyncPeriodTimingChanged(
-            hal::HWDisplayId display, const hal::VsyncPeriodChangeTimeline& timeline) override {
-        mCallback->onComposerHalVsyncPeriodTimingChanged(display, timeline);
-        return Void();
-    }
-
-    Return<void> onSeamlessPossible(hal::HWDisplayId display) override {
-        mCallback->onComposerHalSeamlessPossible(display);
-        return Void();
-    }
-
-private:
-    ComposerCallback* const mCallback;
-    const bool mVsyncSwitchingSupported;
-};
-
-} // namespace
 
 HWComposer::~HWComposer() = default;
 
@@ -149,7 +92,7 @@
     mDisplayData.clear();
 }
 
-void HWComposer::setCallback(HWC2::ComposerCallback* callback) {
+void HWComposer::setCallback(HWC2::ComposerCallback& callback) {
     loadCapabilities();
     loadLayerMetadataSupport();
 
@@ -159,10 +102,7 @@
     }
     mRegisteredCallback = true;
 
-    const bool vsyncSwitchingSupported =
-            mComposer->isSupported(Hwc2::Composer::OptionalFeature::RefreshRateSwitching);
-    mComposer->registerCallback(
-            sp<ComposerCallbackBridge>::make(callback, vsyncSwitchingSupported));
+    mComposer->registerCallback(callback);
 }
 
 bool HWComposer::getDisplayIdentificationData(hal::HWDisplayId hwcDisplayId, uint8_t* outPort,
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 9e57602..3e68028 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -26,6 +26,7 @@
 #include <vector>
 
 #include <android-base/thread_annotations.h>
+#include <ui/DisplayIdentification.h>
 #include <ui/FenceTime.h>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
@@ -38,7 +39,6 @@
 #include <utils/StrongPointer.h>
 #include <utils/Timers.h>
 
-#include "DisplayIdentification.h"
 #include "DisplayMode.h"
 #include "HWC2.h"
 #include "Hal.h"
@@ -105,7 +105,7 @@
 
     virtual ~HWComposer();
 
-    virtual void setCallback(HWC2::ComposerCallback*) = 0;
+    virtual void setCallback(HWC2::ComposerCallback&) = 0;
 
     virtual bool getDisplayIdentificationData(hal::HWDisplayId, uint8_t* outPort,
                                               DisplayIdentificationData* outData) const = 0;
@@ -272,7 +272,7 @@
 
     ~HWComposer() override;
 
-    void setCallback(HWC2::ComposerCallback*) override;
+    void setCallback(HWC2::ComposerCallback&) override;
 
     bool getDisplayIdentificationData(hal::HWDisplayId, uint8_t* outPort,
                                       DisplayIdentificationData* outData) const override;
diff --git a/services/surfaceflinger/DisplayHardware/Hash.cpp b/services/surfaceflinger/DisplayHardware/Hash.cpp
deleted file mode 100644
index 6056c8d..0000000
--- a/services/surfaceflinger/DisplayHardware/Hash.cpp
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2021 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 "DisplayIdentification"
-
-#include <cstring>
-#include <type_traits>
-
-#include <log/log.h>
-
-#include "Hash.h"
-
-namespace android {
-namespace {
-
-template <class T>
-inline T load(const void* p) {
-    static_assert(std::is_integral<T>::value, "T must be integral");
-
-    T r;
-    std::memcpy(&r, p, sizeof(r));
-    return r;
-}
-
-uint64_t rotateByAtLeast1(uint64_t val, uint8_t shift) {
-    return (val >> shift) | (val << (64 - shift));
-}
-
-uint64_t shiftMix(uint64_t val) {
-    return val ^ (val >> 47);
-}
-
-uint64_t hash64Len16(uint64_t u, uint64_t v) {
-    constexpr uint64_t kMul = 0x9ddfea08eb382d69;
-    uint64_t a = (u ^ v) * kMul;
-    a ^= (a >> 47);
-    uint64_t b = (v ^ a) * kMul;
-    b ^= (b >> 47);
-    b *= kMul;
-    return b;
-}
-
-uint64_t hash64Len0To16(const char* s, uint64_t len) {
-    constexpr uint64_t k2 = 0x9ae16a3b2f90404f;
-    constexpr uint64_t k3 = 0xc949d7c7509e6557;
-
-    if (len > 8) {
-        const uint64_t a = load<uint64_t>(s);
-        const uint64_t b = load<uint64_t>(s + len - 8);
-        return hash64Len16(a, rotateByAtLeast1(b + len, static_cast<uint8_t>(len))) ^ b;
-    }
-    if (len >= 4) {
-        const uint32_t a = load<uint32_t>(s);
-        const uint32_t b = load<uint32_t>(s + len - 4);
-        return hash64Len16(len + (a << 3), b);
-    }
-    if (len > 0) {
-        const unsigned char a = static_cast<unsigned char>(s[0]);
-        const unsigned char b = static_cast<unsigned char>(s[len >> 1]);
-        const unsigned char c = static_cast<unsigned char>(s[len - 1]);
-        const uint32_t y = static_cast<uint32_t>(a) + (static_cast<uint32_t>(b) << 8);
-        const uint32_t z = static_cast<uint32_t>(len) + (static_cast<uint32_t>(c) << 2);
-        return shiftMix(y * k2 ^ z * k3) * k2;
-    }
-    return k2;
-}
-
-} // namespace
-
-uint64_t cityHash64Len0To16(std::string_view sv) {
-    auto len = sv.length();
-    if (len > 16) {
-        ALOGE("%s called with length %zu. Only hashing the first 16 chars", __FUNCTION__, len);
-        len = 16;
-    }
-    return hash64Len0To16(sv.data(), len);
-}
-
-} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/DisplayHardware/Hash.h b/services/surfaceflinger/DisplayHardware/Hash.h
deleted file mode 100644
index a7b6c71..0000000
--- a/services/surfaceflinger/DisplayHardware/Hash.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2021 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 <cstdint>
-#include <string_view>
-
-namespace android {
-
-// CityHash64 implementation that only hashes at most the first 16 characters of the given string.
-uint64_t cityHash64Len0To16(std::string_view sv);
-
-} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index d3acecb..746ac64 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -30,6 +30,7 @@
 #include <hidl/HidlTransportUtils.h>
 #include <log/log.h>
 #include <utils/Trace.h>
+#include "HWC2.h"
 #include "Hal.h"
 
 #include <algorithm>
@@ -44,6 +45,63 @@
 using hardware::Return;
 
 namespace Hwc2 {
+namespace {
+
+using android::hardware::Return;
+using android::hardware::Void;
+using android::HWC2::ComposerCallback;
+
+class ComposerCallbackBridge : public IComposerCallback {
+public:
+    ComposerCallbackBridge(ComposerCallback& callback, bool vsyncSwitchingSupported)
+          : mCallback(callback), mVsyncSwitchingSupported(vsyncSwitchingSupported) {}
+
+    Return<void> onHotplug(Display display, Connection connection) override {
+        mCallback.onComposerHalHotplug(display, connection);
+        return Void();
+    }
+
+    Return<void> onRefresh(Display display) override {
+        mCallback.onComposerHalRefresh(display);
+        return Void();
+    }
+
+    Return<void> onVsync(Display display, int64_t timestamp) override {
+        if (!mVsyncSwitchingSupported) {
+            mCallback.onComposerHalVsync(display, timestamp, std::nullopt);
+        } else {
+            ALOGW("Unexpected onVsync callback on composer >= 2.4, ignoring.");
+        }
+        return Void();
+    }
+
+    Return<void> onVsync_2_4(Display display, int64_t timestamp,
+                             VsyncPeriodNanos vsyncPeriodNanos) override {
+        if (mVsyncSwitchingSupported) {
+            mCallback.onComposerHalVsync(display, timestamp, vsyncPeriodNanos);
+        } else {
+            ALOGW("Unexpected onVsync_2_4 callback on composer <= 2.3, ignoring.");
+        }
+        return Void();
+    }
+
+    Return<void> onVsyncPeriodTimingChanged(Display display,
+                                            const VsyncPeriodChangeTimeline& timeline) override {
+        mCallback.onComposerHalVsyncPeriodTimingChanged(display, timeline);
+        return Void();
+    }
+
+    Return<void> onSeamlessPossible(Display display) override {
+        mCallback.onComposerHalSeamlessPossible(display);
+        return Void();
+    }
+
+private:
+    ComposerCallback& mCallback;
+    const bool mVsyncSwitchingSupported;
+};
+
+} // namespace
 
 HidlComposer::~HidlComposer() = default;
 
@@ -1246,6 +1304,13 @@
     return Error::NONE;
 }
 
+void HidlComposer::registerCallback(ComposerCallback& callback) {
+    const bool vsyncSwitchingSupported =
+            isSupported(Hwc2::Composer::OptionalFeature::RefreshRateSwitching);
+
+    registerCallback(sp<ComposerCallbackBridge>::make(callback, vsyncSwitchingSupported));
+}
+
 CommandReader::~CommandReader() {
     resetData();
 }
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index c8c7800..1ffca6e 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -172,7 +172,7 @@
     std::vector<IComposer::Capability> getCapabilities() override;
     std::string dumpDebugInfo() override;
 
-    void registerCallback(const sp<IComposerCallback>& callback) override;
+    void registerCallback(HWC2::ComposerCallback& callback) override;
 
     // Reset all pending commands in the command buffer. Useful if you want to
     // skip a frame but have already queued some commands.
@@ -334,6 +334,8 @@
         ~CommandWriter() override {}
     };
 
+    void registerCallback(const sp<IComposerCallback>& callback);
+
     // Many public functions above simply write a command into the command
     // queue to batch the calls.  validateDisplay and presentDisplay will call
     // this function to execute the command queue.
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index 28d28f4..0db56aa 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -22,8 +22,8 @@
 
 #include <utils/Mutex.h>
 
+#include <ui/DisplayIdentification.h>
 #include "../Scheduler/OneShotTimer.h"
-#include "DisplayIdentification.h"
 
 using namespace std::chrono_literals;
 
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
index 7720713..307da41 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
@@ -25,7 +25,7 @@
 #include <gui/IGraphicBufferProducer.h>
 #include <ui/DisplayId.h>
 
-#include "DisplayIdentification.h"
+#include <ui/DisplayIdentification.h>
 
 namespace android {
 
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index fa2c92d..9fffd12 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -945,16 +945,10 @@
     setTransactionFlags(eTransactionNeeded);
     return true;
 }
-bool Layer::setMatrix(const layer_state_t::matrix22_t& matrix,
-        bool allowNonRectPreservingTransforms) {
+bool Layer::setMatrix(const layer_state_t::matrix22_t& matrix) {
     ui::Transform t;
     t.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy);
 
-    if (!allowNonRectPreservingTransforms && !t.preserveRects()) {
-        ALOGW("Attempt to set rotation matrix without permission ACCESS_SURFACE_FLINGER nor "
-              "ROTATE_SURFACE_FLINGER ignored");
-        return false;
-    }
     mDrawingState.sequence++;
     mDrawingState.transform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy);
     mDrawingState.modified = true;
@@ -2167,25 +2161,15 @@
 }
 
 void Layer::fillInputFrameInfo(WindowInfo& info, const ui::Transform& displayTransform) {
-    // Transform layer size to screen space and inset it by surface insets.
-    // If this is a portal window, set the touchableRegion to the layerBounds.
-    Rect layerBounds = info.portalToDisplayId == ADISPLAY_ID_NONE
-            ? getInputBounds()
-            : info.touchableRegion.getBounds();
+    Rect layerBounds = getInputBounds();
     if (!layerBounds.isValid()) {
-        layerBounds = getInputBounds();
-    }
-
-    if (!layerBounds.isValid()) {
-        // If the layer bounds is empty, set the frame to empty and clear the transform
-        info.frameLeft = 0;
-        info.frameTop = 0;
-        info.frameRight = 0;
-        info.frameBottom = 0;
-        info.transform.reset();
-        info.touchableRegion = Region();
         info.flags = WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::NOT_FOCUSABLE;
-        return;
+        info.touchableRegion.clear();
+        // A layer could have invalid input bounds and still expect to receive touch input if it has
+        // replaceTouchableRegionWithCrop. For that case, the input transform needs to be calculated
+        // correctly to determine the coordinate space for input events. Use an empty rect so that
+        // the layer will receive input in its own layer space.
+        layerBounds = Rect::EMPTY_RECT;
     }
 
     const ui::Transform layerTransform = getInputTransform();
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 4cdd8fa..40fb946 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -98,6 +98,7 @@
     uid_t callingUid;
     uint32_t textureName;
     std::optional<uint32_t> sequence = std::nullopt;
+    bool addToRoot = true;
 };
 
 class Layer : public virtual RefBase, compositionengine::LayerFE {
@@ -366,8 +367,7 @@
     // Set a 2x2 transformation matrix on the layer. This transform
     // will be applied after parent transforms, but before any final
     // producer specified transform.
-    virtual bool setMatrix(const layer_state_t::matrix22_t& matrix,
-                           bool allowNonRectPreservingTransforms);
+    virtual bool setMatrix(const layer_state_t::matrix22_t& matrix);
 
     // This second set of geometry attributes are controlled by
     // setGeometryAppliesWithResize, and their default mode is to be
@@ -959,6 +959,19 @@
     bool usingRelativeZ(LayerVector::StateSet) const;
 
     virtual ui::Transform getInputTransform() const;
+    /**
+     * Get the bounds in layer space within which this layer can receive input.
+     *
+     * These bounds are used to:
+     * - Determine the input frame for the layer to be used for occlusion detection; and
+     * - Determine the coordinate space within which the layer will receive input. The top-left of
+     *   this rect will be the origin of the coordinate space that the input events sent to the
+     *   layer will be in (prior to accounting for surface insets).
+     *
+     * The layer can still receive touch input if these bounds are invalid if
+     * "replaceTouchableRegionWithCrop" is specified. In this case, the layer will receive input
+     * in this layer's space, regardless of the specified crop layer.
+     */
     virtual Rect getInputBounds() const;
 
     // constant
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 89d861f..3b9cfa6 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -38,6 +38,8 @@
 namespace android::scheduler {
 namespace {
 
+constexpr RefreshRateConfigs::GlobalSignals kNoSignals;
+
 std::string formatLayerInfo(const RefreshRateConfigs::LayerRequirement& layer, float weight) {
     return base::StringPrintf("%s (type=%s, weight=%.2f seamlessness=%s) %s", layer.name.c_str(),
                               ftl::enum_string(layer.vote).c_str(), weight,
@@ -225,7 +227,7 @@
     // The layer frame rate is not a divider of the refresh rate,
     // there is a small penalty attached to the score to favor the frame rates
     // the exactly matches the display refresh rate or a multiple.
-    constexpr float kNonExactMatchingPenalty = 0.99f;
+    constexpr float kNonExactMatchingPenalty = 0.95f;
     return calculateNonExactMatchingLayerScoreLocked(layer, refreshRate) * seamlessness *
             kNonExactMatchingPenalty;
 }
@@ -235,63 +237,26 @@
     float score;
 };
 
-RefreshRate RefreshRateConfigs::getBestRefreshRate(const std::vector<LayerRequirement>& layers,
-                                                   GlobalSignals globalSignals,
-                                                   GlobalSignals* outSignalsConsidered) const {
+auto RefreshRateConfigs::getBestRefreshRate(const std::vector<LayerRequirement>& layers,
+                                            GlobalSignals signals) const
+        -> std::pair<RefreshRate, GlobalSignals> {
     std::lock_guard lock(mLock);
 
-    if (auto cached = getCachedBestRefreshRate(layers, globalSignals, outSignalsConsidered)) {
-        return *cached;
+    if (mGetBestRefreshRateCache &&
+        mGetBestRefreshRateCache->arguments == std::make_pair(layers, signals)) {
+        return mGetBestRefreshRateCache->result;
     }
 
-    GlobalSignals signalsConsidered;
-    RefreshRate result = getBestRefreshRateLocked(layers, globalSignals, &signalsConsidered);
-    lastBestRefreshRateInvocation.emplace(
-            GetBestRefreshRateInvocation{.layerRequirements = layers,
-                                         .globalSignals = globalSignals,
-                                         .outSignalsConsidered = signalsConsidered,
-                                         .resultingBestRefreshRate = result});
-    if (outSignalsConsidered) {
-        *outSignalsConsidered = signalsConsidered;
-    }
+    const auto result = getBestRefreshRateLocked(layers, signals);
+    mGetBestRefreshRateCache = GetBestRefreshRateCache{{layers, signals}, result};
     return result;
 }
 
-std::optional<RefreshRate> RefreshRateConfigs::getCachedBestRefreshRate(
-        const std::vector<LayerRequirement>& layers, GlobalSignals globalSignals,
-        GlobalSignals* outSignalsConsidered) const {
-    const bool sameAsLastCall = lastBestRefreshRateInvocation &&
-            lastBestRefreshRateInvocation->layerRequirements == layers &&
-            lastBestRefreshRateInvocation->globalSignals == globalSignals;
-
-    if (sameAsLastCall) {
-        if (outSignalsConsidered) {
-            *outSignalsConsidered = lastBestRefreshRateInvocation->outSignalsConsidered;
-        }
-        return lastBestRefreshRateInvocation->resultingBestRefreshRate;
-    }
-
-    return {};
-}
-
-RefreshRate RefreshRateConfigs::getBestRefreshRateLocked(
-        const std::vector<LayerRequirement>& layers, GlobalSignals globalSignals,
-        GlobalSignals* outSignalsConsidered) const {
+auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vector<LayerRequirement>& layers,
+                                                  GlobalSignals signals) const
+        -> std::pair<RefreshRate, GlobalSignals> {
     ATRACE_CALL();
-    ALOGV("getBestRefreshRate %zu layers", layers.size());
-
-    if (outSignalsConsidered) *outSignalsConsidered = {};
-    const auto setTouchConsidered = [&] {
-        if (outSignalsConsidered) {
-            outSignalsConsidered->touch = true;
-        }
-    };
-
-    const auto setIdleConsidered = [&] {
-        if (outSignalsConsidered) {
-            outSignalsConsidered->idle = true;
-        }
-    };
+    ALOGV("%s: %zu layers", __func__, layers.size());
 
     int noVoteLayers = 0;
     int minVoteLayers = 0;
@@ -301,6 +266,7 @@
     int explicitExact = 0;
     float maxExplicitWeight = 0;
     int seamedFocusedLayers = 0;
+
     for (const auto& layer : layers) {
         switch (layer.vote) {
             case LayerVoteType::NoVote:
@@ -349,10 +315,9 @@
 
     // Consider the touch event if there are no Explicit* layers. Otherwise wait until after we've
     // selected a refresh rate to see if we should apply touch boost.
-    if (globalSignals.touch && !hasExplicitVoteLayers) {
+    if (signals.touch && !hasExplicitVoteLayers) {
         ALOGV("TouchBoost - choose %s", getMaxRefreshRateByPolicyLocked().getName().c_str());
-        setTouchConsidered();
-        return getMaxRefreshRateByPolicyLocked(anchorGroup);
+        return {getMaxRefreshRateByPolicyLocked(anchorGroup), GlobalSignals{.touch = true}};
     }
 
     // If the primary range consists of a single refresh rate then we can only
@@ -361,23 +326,21 @@
     const bool primaryRangeIsSingleRate =
             isApproxEqual(policy->primaryRange.min, policy->primaryRange.max);
 
-    if (!globalSignals.touch && globalSignals.idle &&
-        !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
+    if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
         ALOGV("Idle - choose %s", getMinRefreshRateByPolicyLocked().getName().c_str());
-        setIdleConsidered();
-        return getMinRefreshRateByPolicyLocked();
+        return {getMinRefreshRateByPolicyLocked(), GlobalSignals{.idle = true}};
     }
 
     if (layers.empty() || noVoteLayers == layers.size()) {
         const auto& refreshRate = getMaxRefreshRateByPolicyLocked(anchorGroup);
         ALOGV("no layers with votes - choose %s", refreshRate.getName().c_str());
-        return refreshRate;
+        return {refreshRate, kNoSignals};
     }
 
     // Only if all layers want Min we should return Min
     if (noVoteLayers + minVoteLayers == layers.size()) {
         ALOGV("all layers Min - choose %s", getMinRefreshRateByPolicyLocked().getName().c_str());
-        return getMinRefreshRateByPolicyLocked();
+        return {getMinRefreshRateByPolicyLocked(), kNoSignals};
     }
 
     // Find the best refresh rate based on score
@@ -466,9 +429,9 @@
                         [](RefreshRateScore score) { return score.score == 0; })) {
             const auto& refreshRate = getMaxRefreshRateByPolicyLocked(anchorGroup);
             ALOGV("layers not scored - choose %s", refreshRate.getName().c_str());
-            return refreshRate;
+            return {refreshRate, kNoSignals};
         } else {
-            return *bestRefreshRate;
+            return {*bestRefreshRate, kNoSignals};
         }
     }
 
@@ -490,14 +453,13 @@
 
     using fps_approx_ops::operator<;
 
-    if (globalSignals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
+    if (signals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
         bestRefreshRate->getFps() < touchRefreshRate.getFps()) {
-        setTouchConsidered();
         ALOGV("TouchBoost - choose %s", touchRefreshRate.getName().c_str());
-        return touchRefreshRate;
+        return {touchRefreshRate, GlobalSignals{.touch = true}};
     }
 
-    return *bestRefreshRate;
+    return {*bestRefreshRate, kNoSignals};
 }
 
 std::unordered_map<uid_t, std::vector<const RefreshRateConfigs::LayerRequirement*>>
@@ -699,7 +661,7 @@
 
     // Invalidate the cached invocation to getBestRefreshRate. This forces
     // the refresh rate to be recomputed on the next call to getBestRefreshRate.
-    lastBestRefreshRateInvocation.reset();
+    mGetBestRefreshRateCache.reset();
 
     mCurrentRefreshRate = mRefreshRates.at(modeId).get();
 }
@@ -741,7 +703,7 @@
 
     // Invalidate the cached invocation to getBestRefreshRate. This forces
     // the refresh rate to be recomputed on the next call to getBestRefreshRate.
-    lastBestRefreshRateInvocation.reset();
+    mGetBestRefreshRateCache.reset();
 
     mRefreshRates.clear();
     for (const auto& mode : modes) {
@@ -800,7 +762,7 @@
         ALOGE("Invalid refresh rate policy: %s", policy.toString().c_str());
         return BAD_VALUE;
     }
-    lastBestRefreshRateInvocation.reset();
+    mGetBestRefreshRateCache.reset();
     Policy previousPolicy = *getCurrentPolicyLocked();
     mDisplayManagerPolicy = policy;
     if (*getCurrentPolicyLocked() == previousPolicy) {
@@ -815,7 +777,7 @@
     if (policy && !isPolicyValidLocked(*policy)) {
         return BAD_VALUE;
     }
-    lastBestRefreshRateInvocation.reset();
+    mGetBestRefreshRateCache.reset();
     Policy previousPolicy = *getCurrentPolicyLocked();
     mOverridePolicy = policy;
     if (*getCurrentPolicyLocked() == previousPolicy) {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 849d297..ade1787 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -20,6 +20,7 @@
 #include <numeric>
 #include <optional>
 #include <type_traits>
+#include <utility>
 
 #include <android-base/stringprintf.h>
 #include <gui/DisplayEventReceiver.h>
@@ -249,11 +250,10 @@
         }
     };
 
-    // Returns the refresh rate that best fits the given layers. outSignalsConsidered returns
-    // whether the refresh rate was chosen based on touch boost and/or idle timer.
-    RefreshRate getBestRefreshRate(const std::vector<LayerRequirement>&, GlobalSignals,
-                                   GlobalSignals* outSignalsConsidered = nullptr) const
-            EXCLUDES(mLock);
+    // Returns the refresh rate that best fits the given layers, and whether the refresh rate was
+    // chosen based on touch boost and/or idle timer.
+    std::pair<RefreshRate, GlobalSignals> getBestRefreshRate(const std::vector<LayerRequirement>&,
+                                                             GlobalSignals) const EXCLUDES(mLock);
 
     FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
         std::lock_guard lock(mLock);
@@ -310,6 +310,9 @@
                                         .idleTimerTimeoutMs = 0,
                                         .supportKernelIdleTimer = false});
 
+    RefreshRateConfigs(const RefreshRateConfigs&) = delete;
+    RefreshRateConfigs& operator=(const RefreshRateConfigs&) = delete;
+
     // Returns whether switching modes (refresh rate or resolution) is possible.
     // TODO(b/158780872): Consider HAL support, and skip frame rate detection if the modes only
     // differ in resolution.
@@ -391,11 +394,8 @@
 
     void dump(std::string& result) const EXCLUDES(mLock);
 
-    RefreshRateConfigs(const RefreshRateConfigs&) = delete;
-    void operator=(const RefreshRateConfigs&) = delete;
-
 private:
-    friend class RefreshRateConfigsTest;
+    friend struct TestableRefreshRateConfigs;
 
     void constructAvailableRefreshRates() REQUIRES(mLock);
 
@@ -403,13 +403,8 @@
             const std::function<bool(const RefreshRate&)>& shouldAddRefreshRate,
             std::vector<const RefreshRate*>* outRefreshRates) REQUIRES(mLock);
 
-    std::optional<RefreshRate> getCachedBestRefreshRate(const std::vector<LayerRequirement>&,
-                                                        GlobalSignals,
-                                                        GlobalSignals* outSignalsConsidered) const
-            REQUIRES(mLock);
-
-    RefreshRate getBestRefreshRateLocked(const std::vector<LayerRequirement>&, GlobalSignals,
-                                         GlobalSignals* outSignalsConsidered) const REQUIRES(mLock);
+    std::pair<RefreshRate, GlobalSignals> getBestRefreshRateLocked(
+            const std::vector<LayerRequirement>&, GlobalSignals) const REQUIRES(mLock);
 
     // Returns the refresh rate with the highest score in the collection specified from begin
     // to end. If there are more than one with the same highest refresh rate, the first one is
@@ -497,14 +492,11 @@
     const Config mConfig;
     bool mSupportsFrameRateOverrideByContent;
 
-    struct GetBestRefreshRateInvocation {
-        std::vector<LayerRequirement> layerRequirements;
-        GlobalSignals globalSignals;
-        GlobalSignals outSignalsConsidered;
-        RefreshRate resultingBestRefreshRate;
+    struct GetBestRefreshRateCache {
+        std::pair<std::vector<LayerRequirement>, GlobalSignals> arguments;
+        std::pair<RefreshRate, GlobalSignals> result;
     };
-    mutable std::optional<GetBestRefreshRateInvocation> lastBestRefreshRateInvocation
-            GUARDED_BY(mLock);
+    mutable std::optional<GetBestRefreshRateCache> mGetBestRefreshRateCache GUARDED_BY(mLock);
 
     // Declare mIdleTimer last to ensure its thread joins before the mutex/callbacks are destroyed.
     std::mutex mIdleTimerCallbacksMutex;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index a85e748..665d36982 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -537,18 +537,19 @@
 
     ATRACE_CALL();
 
-    const auto refreshRateConfigs = holdRefreshRateConfigs();
-    scheduler::LayerHistory::Summary summary =
-            mLayerHistory.summarize(*refreshRateConfigs, systemTime());
-    scheduler::RefreshRateConfigs::GlobalSignals consideredSignals;
     DisplayModePtr newMode;
+    GlobalSignals consideredSignals;
+
     bool frameRateChanged;
     bool frameRateOverridesChanged;
+
+    const auto refreshRateConfigs = holdRefreshRateConfigs();
+    LayerHistory::Summary summary = mLayerHistory.summarize(*refreshRateConfigs, systemTime());
     {
         std::lock_guard<std::mutex> lock(mPolicyLock);
-        mPolicy.contentRequirements = summary;
+        mPolicy.contentRequirements = std::move(summary);
 
-        newMode = calculateRefreshRateModeId(&consideredSignals);
+        std::tie(newMode, consideredSignals) = chooseDisplayMode();
         frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, newMode->getFps());
 
         if (mPolicy.mode == newMode) {
@@ -678,8 +679,7 @@
     mVsyncSchedule->dump(out);
 }
 
-bool Scheduler::updateFrameRateOverrides(
-        scheduler::RefreshRateConfigs::GlobalSignals consideredSignals, Fps displayRefreshRate) {
+bool Scheduler::updateFrameRateOverrides(GlobalSignals consideredSignals, Fps displayRefreshRate) {
     const auto refreshRateConfigs = holdRefreshRateConfigs();
     if (!refreshRateConfigs->supportsFrameRateOverrideByContent()) {
         return false;
@@ -697,9 +697,11 @@
 template <class T>
 bool Scheduler::handleTimerStateChanged(T* currentState, T newState) {
     DisplayModePtr newMode;
+    GlobalSignals consideredSignals;
+
     bool refreshRateChanged = false;
     bool frameRateOverridesChanged;
-    scheduler::RefreshRateConfigs::GlobalSignals consideredSignals;
+
     const auto refreshRateConfigs = holdRefreshRateConfigs();
     {
         std::lock_guard<std::mutex> lock(mPolicyLock);
@@ -707,7 +709,7 @@
             return false;
         }
         *currentState = newState;
-        newMode = calculateRefreshRateModeId(&consideredSignals);
+        std::tie(newMode, consideredSignals) = chooseDisplayMode();
         frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, newMode->getFps());
         if (mPolicy.mode == newMode) {
             // We don't need to change the display mode, but we might need to send an event
@@ -733,33 +735,33 @@
     return consideredSignals.touch;
 }
 
-DisplayModePtr Scheduler::calculateRefreshRateModeId(
-        scheduler::RefreshRateConfigs::GlobalSignals* consideredSignals) {
+auto Scheduler::chooseDisplayMode() -> std::pair<DisplayModePtr, GlobalSignals> {
     ATRACE_CALL();
-    if (consideredSignals) *consideredSignals = {};
 
-    const auto refreshRateConfigs = holdRefreshRateConfigs();
+    const auto configs = holdRefreshRateConfigs();
+
     // If Display Power is not in normal operation we want to be in performance mode. When coming
     // back to normal mode, a grace period is given with DisplayPowerTimer.
     if (mDisplayPowerTimer &&
         (!mPolicy.isDisplayPowerStateNormal || mPolicy.displayPowerTimer == TimerState::Reset)) {
-        return refreshRateConfigs->getMaxRefreshRateByPolicy().getMode();
+        constexpr GlobalSignals kNoSignals;
+        return {configs->getMaxRefreshRateByPolicy().getMode(), kNoSignals};
     }
 
-    const bool touchActive = mTouchTimer && mPolicy.touch == TouchState::Active;
-    const bool idle = mPolicy.idleTimer == TimerState::Expired;
+    const GlobalSignals signals{.touch = mTouchTimer && mPolicy.touch == TouchState::Active,
+                                .idle = mPolicy.idleTimer == TimerState::Expired};
 
-    return refreshRateConfigs
-            ->getBestRefreshRate(mPolicy.contentRequirements, {.touch = touchActive, .idle = idle},
-                                 consideredSignals)
-            .getMode();
+    const auto [refreshRate, consideredSignals] =
+            configs->getBestRefreshRate(mPolicy.contentRequirements, signals);
+
+    return {refreshRate.getMode(), consideredSignals};
 }
 
 DisplayModePtr Scheduler::getPreferredDisplayMode() {
     std::lock_guard<std::mutex> lock(mPolicyLock);
-    // Make sure that the default mode ID is first updated, before returned.
+    // Make sure the stored mode is up to date.
     if (mPolicy.mode) {
-        mPolicy.mode = calculateRefreshRateModeId();
+        mPolicy.mode = chooseDisplayMode().first;
     }
     return mPolicy.mode;
 }
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index bc9024a..468c4cc 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -269,15 +269,14 @@
 
     void setVsyncPeriod(nsecs_t period);
 
-    // This function checks whether individual features that are affecting the refresh rate
-    // selection were initialized, prioritizes them, and calculates the DisplayModeId
-    // for the suggested refresh rate.
-    DisplayModePtr calculateRefreshRateModeId(
-            RefreshRateConfigs::GlobalSignals* consideredSignals = nullptr) REQUIRES(mPolicyLock);
+    using GlobalSignals = RefreshRateConfigs::GlobalSignals;
+
+    // Returns the display mode that fulfills the policy, and the signals that were considered.
+    std::pair<DisplayModePtr, GlobalSignals> chooseDisplayMode() REQUIRES(mPolicyLock);
+
+    bool updateFrameRateOverrides(GlobalSignals, Fps displayRefreshRate) REQUIRES(mPolicyLock);
 
     void dispatchCachedReportedMode() REQUIRES(mPolicyLock) EXCLUDES(mRefreshRateConfigsLock);
-    bool updateFrameRateOverrides(RefreshRateConfigs::GlobalSignals, Fps displayRefreshRate)
-            REQUIRES(mPolicyLock);
 
     impl::EventThread::ThrottleVsyncCallback makeThrottleVsyncCallback() const
             EXCLUDES(mRefreshRateConfigsLock);
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index b586169..61da45d 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -95,6 +95,7 @@
 #include <type_traits>
 #include <unordered_map>
 
+#include <ui/DisplayIdentification.h>
 #include "BackgroundExecutor.h"
 #include "BufferLayer.h"
 #include "BufferQueueLayer.h"
@@ -104,7 +105,6 @@
 #include "ContainerLayer.h"
 #include "DisplayDevice.h"
 #include "DisplayHardware/ComposerHal.h"
-#include "DisplayHardware/DisplayIdentification.h"
 #include "DisplayHardware/FramebufferSurface.h"
 #include "DisplayHardware/HWComposer.h"
 #include "DisplayHardware/Hal.h"
@@ -271,12 +271,6 @@
 }
 
 
-enum Permission {
-    ACCESS_SURFACE_FLINGER = 0x1,
-    ROTATE_SURFACE_FLINGER = 0x2,
-    INTERNAL_SYSTEM_WINDOW = 0x4,
-};
-
 struct IdleTimerConfig {
     int32_t timeoutMs;
     bool supportKernelIdleTimer;
@@ -855,7 +849,7 @@
 
     mCompositionEngine->setTimeStats(mTimeStats);
     mCompositionEngine->setHwComposer(getFactory().createHWComposer(mHwcServiceName));
-    mCompositionEngine->getHwComposer().setCallback(this);
+    mCompositionEngine->getHwComposer().setCallback(*this);
     ClientCache::getInstance().setRenderEngine(&getRenderEngine());
 
     if (base::GetBoolProperty("debug.sf.enable_hwc_vds"s, false)) {
@@ -1020,6 +1014,9 @@
     info->secure = display->isSecure();
     info->deviceProductInfo = display->getDeviceProductInfo();
 
+    // TODO: Scale this to multiple displays.
+    info->installOrientation = display->isPrimary() ? internalDisplayOrientation : ui::ROTATION_0;
+
     return NO_ERROR;
 }
 
@@ -1953,6 +1950,11 @@
     scheduleComposite(FrameHint::kNone);
 }
 
+void SurfaceFlinger::onComposerHalVsyncIdle(hal::HWDisplayId) {
+    // TODO(b/198106220): force enable HWVsync to avoid drift problem during
+    // idle.
+}
+
 void SurfaceFlinger::setVsyncEnabled(bool enabled) {
     ATRACE_CALL();
 
@@ -2124,7 +2126,8 @@
 
         bool needsTraversal = false;
         if (clearTransactionFlags(eTransactionFlushNeeded)) {
-            needsTraversal = flushTransactionQueues(vsyncId);
+            needsTraversal |= commitCreatedLayers();
+            needsTraversal |= flushTransactionQueues(vsyncId);
         }
 
         const bool shouldCommit =
@@ -2166,6 +2169,11 @@
     updateCursorAsync();
     updateInputFlinger();
 
+    if (mLayerTracingEnabled && !mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) {
+        // This will block and tracing should only be enabled for debugging.
+        mLayerTracing.notify(mVisibleRegionsDirty, frameTime);
+    }
+
     MAIN_THREAD_GUARD(persistDisplayBrightness(mustComposite));
 
     return mustComposite && CC_LIKELY(mBootStage != BootStage::BOOTLOADER);
@@ -2267,13 +2275,9 @@
     modulateVsync(&VsyncModulator::onDisplayRefresh, usedGpuComposition);
 
     mLayersWithQueuedFrames.clear();
-    if (mLayerTracingEnabled) {
+    if (mLayerTracingEnabled && mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) {
         // This will block and should only be used for debugging.
-        if (mVisibleRegionsDirty) {
-            mLayerTracing.notify("visibleRegionsDirty");
-        } else if (mLayerTracing.flagIsSet(LayerTracing::TRACE_BUFFERS)) {
-            mLayerTracing.notify("bufferLatched");
-        }
+        mLayerTracing.notify(mVisibleRegionsDirty, frameTime);
     }
 
     mVisibleRegionsWereDirtyThisFrame = mVisibleRegionsDirty; // Cache value for use in post-comp
@@ -3590,7 +3594,7 @@
 }
 
 status_t SurfaceFlinger::addClientLayer(const sp<Client>& client, const sp<IBinder>& handle,
-                                        const sp<Layer>& lbc, const wp<Layer>& parent,
+                                        const sp<Layer>& layer, const wp<Layer>& parent,
                                         bool addToRoot, uint32_t* outTransformHint) {
     if (mNumLayers >= ISurfaceComposer::MAX_LAYERS) {
         ALOGE("AddClientLayer failed, mNumLayers (%zu) >= MAX_LAYERS (%zu)", mNumLayers.load(),
@@ -3598,31 +3602,22 @@
         return NO_MEMORY;
     }
 
-    setLayerCreatedState(handle, lbc, parent, addToRoot);
+    {
+        std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
+        mCreatedLayers.emplace_back(layer, parent, addToRoot);
+    }
 
-    // Create a transaction includes the initial parent and producer.
-    Vector<ComposerState> states;
-    Vector<DisplayState> displays;
-
-    ComposerState composerState;
-    composerState.state.what = layer_state_t::eLayerCreated;
-    composerState.state.surface = handle;
-    states.add(composerState);
-
-    lbc->updateTransformHint(mActiveDisplayTransformHint);
+    layer->updateTransformHint(mActiveDisplayTransformHint);
     if (outTransformHint) {
         *outTransformHint = mActiveDisplayTransformHint;
     }
     // attach this layer to the client
     if (client != nullptr) {
-        client->attachLayer(handle, lbc);
+        client->attachLayer(handle, layer);
     }
 
-    int64_t transactionId = (((int64_t)mPid) << 32) | mUniqueTransactionId++;
-    return setTransactionState(FrameTimelineInfo{}, states, displays, 0 /* flags */, nullptr,
-                               InputWindowCommands{}, -1 /* desiredPresentTime */,
-                               true /* isAutoTimestamp */, {}, false /* hasListenerCallbacks */, {},
-                               transactionId);
+    setTransactionFlags(eTransactionNeeded);
+    return NO_ERROR;
 }
 
 uint32_t SurfaceFlinger::getTransactionFlags() const {
@@ -3980,19 +3975,20 @@
     ATRACE_CALL();
 
     uint32_t permissions =
-            callingThreadHasUnscopedSurfaceFlingerAccess() ? Permission::ACCESS_SURFACE_FLINGER : 0;
+        callingThreadHasUnscopedSurfaceFlingerAccess() ?
+        layer_state_t::Permission::ACCESS_SURFACE_FLINGER : 0;
     // Avoid checking for rotation permissions if the caller already has ACCESS_SURFACE_FLINGER
     // permissions.
-    if ((permissions & Permission::ACCESS_SURFACE_FLINGER) ||
+    if ((permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) ||
         callingThreadHasRotateSurfaceFlingerAccess()) {
-        permissions |= Permission::ROTATE_SURFACE_FLINGER;
+        permissions |= layer_state_t::Permission::ROTATE_SURFACE_FLINGER;
     }
 
     if (callingThreadHasInternalSystemWindowAccess()) {
-        permissions |= Permission::INTERNAL_SYSTEM_WINDOW;
+        permissions |= layer_state_t::Permission::INTERNAL_SYSTEM_WINDOW;
     }
 
-    if (!(permissions & Permission::ACCESS_SURFACE_FLINGER) &&
+    if (!(permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) &&
         (flags & (eEarlyWakeupStart | eEarlyWakeupEnd))) {
         ALOGE("Only WindowManager is allowed to use eEarlyWakeup[Start|End] flags");
         flags &= ~(eEarlyWakeupStart | eEarlyWakeupEnd);
@@ -4054,7 +4050,8 @@
 
     uint32_t clientStateFlags = 0;
     for (const ComposerState& state : states) {
-        clientStateFlags |= setClientStateLocked(frameTimelineInfo, state, desiredPresentTime,
+        ComposerState stateCopy = state;
+        clientStateFlags |= setClientStateLocked(frameTimelineInfo, stateCopy, desiredPresentTime,
                                                  isAutoTimestamp, postTime, permissions);
         if ((flags & eAnimation) && state.state.surface) {
             if (const auto layer = fromHandle(state.state.surface).promote()) {
@@ -4068,7 +4065,7 @@
 
     transactionFlags |= clientStateFlags;
 
-    if (permissions & Permission::ACCESS_SURFACE_FLINGER) {
+    if (permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) {
         transactionFlags |= addInputWindowCommands(inputWindowCommands);
     } else if (!inputWindowCommands.empty()) {
         ALOGE("Only privileged callers are allowed to send input commands.");
@@ -4179,11 +4176,11 @@
 }
 
 uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTimelineInfo,
-                                              const ComposerState& composerState,
+                                              ComposerState& composerState,
                                               int64_t desiredPresentTime, bool isAutoTimestamp,
                                               int64_t postTime, uint32_t permissions) {
-    const layer_state_t& s = composerState.state;
-    const bool privileged = permissions & Permission::ACCESS_SURFACE_FLINGER;
+    layer_state_t& s = composerState.state;
+    s.sanitize(permissions);
 
     std::vector<ListenerCallbacks> filteredListeners;
     for (auto& listener : s.listeners) {
@@ -4207,15 +4204,7 @@
     uint32_t flags = 0;
     sp<Layer> layer = nullptr;
     if (s.surface) {
-        if (what & layer_state_t::eLayerCreated) {
-            layer = handleLayerCreatedLocked(s.surface);
-            if (layer) {
-                flags |= eTransactionNeeded | eTraversalNeeded;
-                mLayersAdded = true;
-            }
-        } else {
-            layer = fromHandle(s.surface).promote();
-        }
+        layer = fromHandle(s.surface).promote();
     } else {
         // The client may provide us a null handle. Treat it as if the layer was removed.
         ALOGW("Attempt to set client state with a null layer handle");
@@ -4301,43 +4290,14 @@
         }
     }
     if (what & layer_state_t::eMatrixChanged) {
-        // TODO: b/109894387
-        //
-        // SurfaceFlinger's renderer is not prepared to handle cropping in the face of arbitrary
-        // rotation. To see the problem observe that if we have a square parent, and a child
-        // of the same size, then we rotate the child 45 degrees around it's center, the child
-        // must now be cropped to a non rectangular 8 sided region.
-        //
-        // Of course we can fix this in the future. For now, we are lucky, SurfaceControl is
-        // private API, and arbitrary rotation is used in limited use cases, for instance:
-        // - WindowManager only uses rotation in one case, which is on a top level layer in which
-        //   cropping is not an issue.
-        // - Launcher, as a privileged app, uses this to transition an application to PiP
-        //   (picture-in-picture) mode.
-        //
-        // However given that abuse of rotation matrices could lead to surfaces extending outside
-        // of cropped areas, we need to prevent non-root clients without permission
-        // ACCESS_SURFACE_FLINGER nor ROTATE_SURFACE_FLINGER
-        // (a.k.a. everyone except WindowManager / tests / Launcher) from setting non rectangle
-        // preserving transformations.
-        const bool allowNonRectPreservingTransforms =
-                permissions & Permission::ROTATE_SURFACE_FLINGER;
-        if (layer->setMatrix(s.matrix, allowNonRectPreservingTransforms)) flags |= eTraversalNeeded;
+        if (layer->setMatrix(s.matrix)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eTransparentRegionChanged) {
         if (layer->setTransparentRegionHint(s.transparentRegion))
             flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eFlagsChanged) {
-        auto changedFlags = s.flags;
-        if (changedFlags & layer_state_t::eLayerIsDisplayDecoration) {
-            if ((permissions & Permission::INTERNAL_SYSTEM_WINDOW) == 0) {
-                changedFlags &= ~layer_state_t::eLayerIsDisplayDecoration;
-                ALOGE("Attempt to use eLayerIsDisplayDecoration without permission "
-                      "INTERNAL_SYSTEM_WINDOW!");
-            }
-        }
-        if (layer->setFlags(changedFlags, s.mask)) flags |= eTraversalNeeded;
+        if (layer->setFlags(s.flags, s.mask)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eCornerRadiusChanged) {
         if (layer->setCornerRadius(s.cornerRadius))
@@ -4395,12 +4355,8 @@
         if (layer->setSidebandStream(s.sidebandStream)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eInputInfoChanged) {
-        if (privileged) {
-            layer->setInputInfo(*s.windowInfoHandle->getInfo());
-            flags |= eTraversalNeeded;
-        } else {
-            ALOGE("Attempt to update WindowInfo without permission ACCESS_SURFACE_FLINGER");
-        }
+        layer->setInputInfo(*s.windowInfoHandle->getInfo());
+        flags |= eTraversalNeeded;
     }
     std::optional<nsecs_t> dequeueBufferTimestamp;
     if (what & layer_state_t::eMetadataChanged) {
@@ -4424,22 +4380,19 @@
         if (layer->setShadowRadius(s.shadowRadius)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eFrameRateSelectionPriority) {
-        if (privileged && layer->setFrameRateSelectionPriority(s.frameRateSelectionPriority)) {
+        if (layer->setFrameRateSelectionPriority(s.frameRateSelectionPriority)) {
             flags |= eTraversalNeeded;
         }
     }
     if (what & layer_state_t::eFrameRateChanged) {
-        if (ValidateFrameRate(s.frameRate, s.frameRateCompatibility, s.changeFrameRateStrategy,
-                              "SurfaceFlinger::setClientStateLocked", privileged)) {
-            const auto compatibility =
-                    Layer::FrameRate::convertCompatibility(s.frameRateCompatibility);
-            const auto strategy =
-                    Layer::FrameRate::convertChangeFrameRateStrategy(s.changeFrameRateStrategy);
+        const auto compatibility =
+            Layer::FrameRate::convertCompatibility(s.frameRateCompatibility);
+        const auto strategy =
+            Layer::FrameRate::convertChangeFrameRateStrategy(s.changeFrameRateStrategy);
 
-            if (layer->setFrameRate(
-                        Layer::FrameRate(Fps::fromValue(s.frameRate), compatibility, strategy))) {
-                flags |= eTraversalNeeded;
-            }
+        if (layer->setFrameRate(
+                Layer::FrameRate(Fps::fromValue(s.frameRate), compatibility, strategy))) {
+          flags |= eTraversalNeeded;
         }
     }
     if (what & layer_state_t::eFixedTransformHintChanged) {
@@ -4451,12 +4404,8 @@
         layer->setAutoRefresh(s.autoRefresh);
     }
     if (what & layer_state_t::eTrustedOverlayChanged) {
-        if (privileged) {
-            if (layer->setTrustedOverlay(s.isTrustedOverlay)) {
-                flags |= eTraversalNeeded;
-            }
-        } else {
-            ALOGE("Attempt to set trusted overlay without permission ACCESS_SURFACE_FLINGER");
+        if (layer->setTrustedOverlay(s.isTrustedOverlay)) {
+            flags |= eTraversalNeeded;
         }
     }
     if (what & layer_state_t::eStretchChanged) {
@@ -4475,13 +4424,9 @@
         }
     }
     if (what & layer_state_t::eDropInputModeChanged) {
-        if (privileged) {
-            if (layer->setDropInputMode(s.dropInputMode)) {
-                flags |= eTraversalNeeded;
-                mInputInfoChanged = true;
-            }
-        } else {
-            ALOGE("Attempt to update DropInputMode without permission ACCESS_SURFACE_FLINGER");
+        if (layer->setDropInputMode(s.dropInputMode)) {
+            flags |= eTraversalNeeded;
+            mInputInfoChanged = true;
         }
     }
     // This has to happen after we reparent children because when we reparent to null we remove
@@ -4559,7 +4504,7 @@
                                                 args.name, mirrorFrom->sequence);
     }
     return addClientLayer(args.client, *outHandle, mirrorLayer /* layer */, nullptr /* parent */,
-                          false /* addAsRoot */, nullptr /* outTransformHint */);
+                          false /* addToRoot */, nullptr /* outTransformHint */);
 }
 
 status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, sp<IBinder>* outHandle,
@@ -4599,7 +4544,7 @@
         return result;
     }
 
-    bool addToRoot = callingThreadHasUnscopedSurfaceFlingerAccess();
+    bool addToRoot = args.addToRoot && callingThreadHasUnscopedSurfaceFlingerAccess();
     wp<Layer> parent(parentHandle != nullptr ? fromHandle(parentHandle) : parentLayer);
     if (parentHandle != nullptr && parent == nullptr) {
         ALOGE("Invalid parent handle %p.", parentHandle.get());
@@ -4626,7 +4571,6 @@
         return result;
     }
 
-    setTransactionFlags(eTransactionNeeded);
     *outLayerId = layer->sequence;
     return result;
 }
@@ -5589,9 +5533,9 @@
         code == IBinder::SYSPROPS_TRANSACTION) {
         return OK;
     }
-    // Numbers from 1000 to 1041 are currently used for backdoors. The code
+    // Numbers from 1000 to 1042 are currently used for backdoors. The code
     // in onTransact verifies that the user is root, and has access to use SF.
-    if (code >= 1000 && code <= 1041) {
+    if (code >= 1000 && code <= 1042) {
         ALOGV("Accessing SurfaceFlinger through backdoor code: %u", code);
         return OK;
     }
@@ -5778,12 +5722,18 @@
             }
             case 1025: { // Set layer tracing
                 n = data.readInt32();
+                int64_t fixedStartingTime = data.readInt64();
                 bool tracingEnabledChanged;
                 if (n) {
                     ALOGD("LayerTracing enabled");
                     tracingEnabledChanged = mLayerTracing.enable();
                     if (tracingEnabledChanged) {
-                        mScheduler->schedule([&]() MAIN_THREAD { mLayerTracing.notify("start"); })
+                        int64_t startingTime =
+                                (fixedStartingTime) ? fixedStartingTime : systemTime();
+                        mScheduler
+                                ->schedule([&]() MAIN_THREAD {
+                                    mLayerTracing.notify("start", startingTime);
+                                })
                                 .wait();
                     }
                 } else {
@@ -6046,6 +5996,16 @@
                 reply->writeInt32(NO_ERROR);
                 return NO_ERROR;
             }
+            case 1042: { // Write layers trace or transaction trace to file
+                if (mTransactionTracing) {
+                    mTransactionTracing->writeToFile();
+                }
+                if (mLayerTracingEnabled) {
+                    mLayerTracing.writeToFile();
+                }
+                reply->writeInt32(NO_ERROR);
+                return NO_ERROR;
+            }
         }
     }
     return err;
@@ -7074,53 +7034,19 @@
     }
 }
 
-void SurfaceFlinger::setLayerCreatedState(const sp<IBinder>& handle, const wp<Layer>& layer,
-                                          const wp<Layer> parent, bool addToRoot) {
-    Mutex::Autolock lock(mCreatedLayersLock);
-    mCreatedLayers[handle->localBinder()] =
-            std::make_unique<LayerCreatedState>(layer, parent, addToRoot);
-}
-
-auto SurfaceFlinger::getLayerCreatedState(const sp<IBinder>& handle) {
-    Mutex::Autolock lock(mCreatedLayersLock);
-    BBinder* b = nullptr;
-    if (handle) {
-        b = handle->localBinder();
-    }
-
-    if (b == nullptr) {
-        return std::unique_ptr<LayerCreatedState>(nullptr);
-    }
-
-    auto it = mCreatedLayers.find(b);
-    if (it == mCreatedLayers.end()) {
-        ALOGE("Can't find layer from handle %p", handle.get());
-        return std::unique_ptr<LayerCreatedState>(nullptr);
-    }
-
-    auto state = std::move(it->second);
-    mCreatedLayers.erase(it);
-    return state;
-}
-
-sp<Layer> SurfaceFlinger::handleLayerCreatedLocked(const sp<IBinder>& handle) {
-    const auto& state = getLayerCreatedState(handle);
-    if (!state) {
-        return nullptr;
-    }
-
-    sp<Layer> layer = state->layer.promote();
+void SurfaceFlinger::handleLayerCreatedLocked(const LayerCreatedState& state) {
+    sp<Layer> layer = state.layer.promote();
     if (!layer) {
-        ALOGE("Invalid layer %p", state->layer.unsafe_get());
-        return nullptr;
+        ALOGD("Layer was destroyed soon after creation %p", state.layer.unsafe_get());
+        return;
     }
 
     sp<Layer> parent;
-    bool addToRoot = state->addToRoot;
-    if (state->initialParent != nullptr) {
-        parent = state->initialParent.promote();
+    bool addToRoot = state.addToRoot;
+    if (state.initialParent != nullptr) {
+        parent = state.initialParent.promote();
         if (parent == nullptr) {
-            ALOGE("Invalid parent %p", state->initialParent.unsafe_get());
+            ALOGD("Parent was destroyed soon after creation %p", state.initialParent.unsafe_get());
             addToRoot = false;
         }
     }
@@ -7140,7 +7066,6 @@
     layer->updateTransformHint(mActiveDisplayTransformHint);
 
     mInterceptor->saveSurfaceCreation(layer);
-    return layer;
 }
 
 void SurfaceFlinger::sample() {
@@ -7222,6 +7147,26 @@
              layerName);
     return buffer;
 }
+
+bool SurfaceFlinger::commitCreatedLayers() {
+    std::vector<LayerCreatedState> createdLayers;
+    {
+        std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
+        createdLayers = std::move(mCreatedLayers);
+        mCreatedLayers.clear();
+        if (createdLayers.size() == 0) {
+            return false;
+        }
+    }
+
+    Mutex::Autolock _l(mStateLock);
+    for (const auto& createdLayer : createdLayers) {
+        handleLayerCreatedLocked(createdLayer);
+    }
+    createdLayers.clear();
+    mLayersAdded = true;
+    return true;
+}
 } // namespace android
 
 #if defined(__gl_h_)
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index ed8d27e..c6a4d85 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -629,6 +629,7 @@
     void onComposerHalVsyncPeriodTimingChanged(hal::HWDisplayId,
                                                const hal::VsyncPeriodChangeTimeline&) override;
     void onComposerHalSeamlessPossible(hal::HWDisplayId) override;
+    void onComposerHalVsyncIdle(hal::HWDisplayId) override;
 
     // ICompositor overrides:
 
@@ -727,7 +728,7 @@
     // Returns true if there is at least one transaction that needs to be flushed
     bool transactionFlushNeeded();
 
-    uint32_t setClientStateLocked(const FrameTimelineInfo&, const ComposerState&,
+    uint32_t setClientStateLocked(const FrameTimelineInfo&, ComposerState&,
                                   int64_t desiredPresentTime, bool isAutoTimestamp,
                                   int64_t postTime, uint32_t permissions) REQUIRES(mStateLock);
 
@@ -1325,7 +1326,7 @@
 
     std::unordered_map<DisplayId, sp<HdrLayerInfoReporter>> mHdrLayerInfoListeners
             GUARDED_BY(mStateLock);
-    mutable Mutex mCreatedLayersLock;
+    mutable std::mutex mCreatedLayersLock;
     struct LayerCreatedState {
         LayerCreatedState(const wp<Layer>& layer, const wp<Layer> parent, bool addToRoot)
               : layer(layer), initialParent(parent), addToRoot(addToRoot) {}
@@ -1341,11 +1342,9 @@
 
     // A temporay pool that store the created layers and will be added to current state in main
     // thread.
-    std::unordered_map<BBinder*, std::unique_ptr<LayerCreatedState>> mCreatedLayers;
-    void setLayerCreatedState(const sp<IBinder>& handle, const wp<Layer>& layer,
-                              const wp<Layer> parent, bool addToRoot);
-    auto getLayerCreatedState(const sp<IBinder>& handle);
-    sp<Layer> handleLayerCreatedLocked(const sp<IBinder>& handle) REQUIRES(mStateLock);
+    std::vector<LayerCreatedState> mCreatedLayers GUARDED_BY(mCreatedLayersLock);
+    bool commitCreatedLayers();
+    void handleLayerCreatedLocked(const LayerCreatedState& state) REQUIRES(mStateLock);
 
     std::atomic<ui::Transform::RotationFlags> mActiveDisplayTransformHint;
 
diff --git a/services/surfaceflinger/Tracing/LayerTracing.cpp b/services/surfaceflinger/Tracing/LayerTracing.cpp
index d136e0b..006efdf 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.cpp
+++ b/services/surfaceflinger/Tracing/LayerTracing.cpp
@@ -98,16 +98,20 @@
     mBuffer->dump(result);
 }
 
-void LayerTracing::notify(const char* where) {
-    ATRACE_CALL();
+void LayerTracing::notify(bool visibleRegionDirty, int64_t time) {
     std::scoped_lock lock(mTraceLock);
     if (!mEnabled) {
         return;
     }
 
+    if (!visibleRegionDirty && !flagIsSet(LayerTracing::TRACE_BUFFERS)) {
+        return;
+    }
+
     ATRACE_CALL();
     LayersTraceProto entry;
-    entry.set_elapsed_realtime_nanos(elapsedRealtimeNano());
+    entry.set_elapsed_realtime_nanos(time);
+    const char* where = visibleRegionDirty ? "visibleRegionsDirty" : "bufferLatched";
     entry.set_where(where);
     LayersProto layers(mFlinger.dumpDrawingStateProto(mFlags));
 
diff --git a/services/surfaceflinger/Tracing/LayerTracing.h b/services/surfaceflinger/Tracing/LayerTracing.h
index 8ca3587..bd448c9 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.h
+++ b/services/surfaceflinger/Tracing/LayerTracing.h
@@ -47,7 +47,7 @@
     bool isEnabled() const;
     status_t writeToFile();
     LayersTraceFileProto createTraceFileProto() const;
-    void notify(const char* where);
+    void notify(bool visibleRegionDirty, int64_t time);
 
     enum : uint32_t {
         TRACE_INPUT = 1 << 1,
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp
index 5136295..a46b795 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.cpp
+++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp
@@ -128,7 +128,9 @@
             mCommittedTransactions.clear();
         } // unlock mMainThreadLock
 
-        addEntry(committedTransactions, removedLayers);
+        if (!committedTransactions.empty() || !removedLayers.empty()) {
+            addEntry(committedTransactions, removedLayers);
+        }
     }
 }
 
@@ -275,6 +277,7 @@
 
 void TransactionTracing::updateStartingStateLocked(
         const proto::TransactionTraceEntry& removedEntry) {
+    mStartingTimestamp = removedEntry.elapsed_realtime_nanos();
     // Keep track of layer starting state so we can reconstruct the layer state as we purge
     // transactions from the buffer.
     for (const proto::LayerCreationArgs& addedLayer : removedEntry.added_layers()) {
@@ -303,13 +306,14 @@
 }
 
 void TransactionTracing::addStartingStateToProtoLocked(proto::TransactionTraceFile& proto) {
-    proto::TransactionTraceEntry* entryProto = proto.add_entry();
-    entryProto->set_elapsed_realtime_nanos(mStartingTimestamp);
-    entryProto->set_vsync_id(0);
     if (mStartingStates.size() == 0) {
         return;
     }
 
+    proto::TransactionTraceEntry* entryProto = proto.add_entry();
+    entryProto->set_elapsed_realtime_nanos(mStartingTimestamp);
+    entryProto->set_vsync_id(0);
+
     entryProto->mutable_added_layers()->Reserve(static_cast<int32_t>(mStartingStates.size()));
     for (auto& [layerId, state] : mStartingStates) {
         entryProto->mutable_added_layers()->Add(TransactionProtoParser::toProto(state.args));
diff --git a/services/surfaceflinger/fuzzer/Android.bp b/services/surfaceflinger/fuzzer/Android.bp
new file mode 100644
index 0000000..7eebd9b
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/Android.bp
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2021 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_defaults {
+    name: "surfaceflinger_fuzz_defaults",
+    include_dirs: [
+        "frameworks/native/services/surfaceflinger/tests/unittests",
+    ],
+    static_libs: [
+        "android.hardware.graphics.composer@2.1-resources",
+        "libgmock",
+        "libgui_mocks",
+        "libgmock_ndk",
+        "libgmock_main",
+        "libgtest_ndk_c++",
+        "libgmock_main_ndk",
+        "librenderengine_mocks",
+        "perfetto_trace_protos",
+        "libcompositionengine_mocks",
+        "perfetto_trace_protos",
+    ],
+    shared_libs: [
+        "libprotoutil",
+        "libstatssocket",
+        "libstatspull",
+        "libtimestats",
+        "libtimestats_proto",
+        "libprotobuf-cpp-full",
+        "android.hardware.graphics.mapper@2.0",
+        "android.hardware.graphics.mapper@3.0",
+        "android.hardware.graphics.mapper@4.0",
+    ],
+    srcs: [
+        ":libsurfaceflinger_sources",
+        ":libsurfaceflinger_mock_sources",
+    ],
+    defaults: [
+        "libsurfaceflinger_defaults",
+    ],
+    header_libs: [
+        "libui_fuzzableDataspaces_headers",
+        "libsurfaceflinger_headers",
+        "libui_headers",
+    ],
+    cflags: [
+        "-Wno-unused-result",
+        "-Wno-conversion",
+        "-Wno-sign-compare",
+    ],
+    fuzz_config: {
+        cc: [
+            "android-media-fuzzing-reports@google.com",
+        ],
+        componentid: 155276,
+    },
+}
+
+cc_fuzz {
+    name: "surfaceflinger_fuzzer",
+    defaults: [
+        "surfaceflinger_fuzz_defaults",
+    ],
+    srcs: [
+        "surfaceflinger_fuzzer.cpp",
+    ],
+}
diff --git a/services/surfaceflinger/fuzzer/README.md b/services/surfaceflinger/fuzzer/README.md
new file mode 100644
index 0000000..7b244fc
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/README.md
@@ -0,0 +1,24 @@
+# Fuzzers for SurfaceFlinger
+## Table of contents
++ [SurfaceFlinger](#SurfaceFlinger)
+
+# <a name="SurfaceFlinger"></a> Fuzzer for SurfaceFlinger
+
+SurfaceFlinger supports the following data sources:
+1. Pixel Formats (parameter name: `defaultCompositionPixelFormat`)
+2. Data Spaces (parameter name: `defaultCompositionDataspace`)
+3. Rotations (parameter name: `internalDisplayOrientation`)
+3. Surface composer tags (parameter name: `onTransact`)
+
+You can find the possible values in the fuzzer's source code.
+
+#### Steps to run
+1. Build the fuzzer
+```
+  $ mm -j$(nproc) surfaceflinger_fuzzer
+```
+2. To run on device
+```
+  $ adb sync data
+  $ adb shell /data/fuzz/arm64/surfaceflinger_fuzzer/surfaceflinger_fuzzer
+```
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
new file mode 100644
index 0000000..4f89cd9
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2021 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 <FuzzableDataspaces.h>
+#include <binder/IServiceManager.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <ui/DisplayStatInfo.h>
+#include "surfaceflinger_fuzzers_utils.h"
+
+namespace android::fuzz {
+
+static constexpr LatchUnsignaledConfig kLatchUnsignaledConfig[] = {
+        LatchUnsignaledConfig::Always,
+        LatchUnsignaledConfig::Auto,
+        LatchUnsignaledConfig::Disabled,
+};
+
+static constexpr ui::PixelFormat kPixelFormats[] = {ui::PixelFormat::RGBA_8888,
+                                                    ui::PixelFormat::RGBX_8888,
+                                                    ui::PixelFormat::RGB_888,
+                                                    ui::PixelFormat::RGB_565,
+                                                    ui::PixelFormat::BGRA_8888,
+                                                    ui::PixelFormat::YCBCR_422_SP,
+                                                    ui::PixelFormat::YCRCB_420_SP,
+                                                    ui::PixelFormat::YCBCR_422_I,
+                                                    ui::PixelFormat::RGBA_FP16,
+                                                    ui::PixelFormat::RAW16,
+                                                    ui::PixelFormat::BLOB,
+                                                    ui::PixelFormat::IMPLEMENTATION_DEFINED,
+                                                    ui::PixelFormat::YCBCR_420_888,
+                                                    ui::PixelFormat::RAW_OPAQUE,
+                                                    ui::PixelFormat::RAW10,
+                                                    ui::PixelFormat::RAW12,
+                                                    ui::PixelFormat::RGBA_1010102,
+                                                    ui::PixelFormat::Y8,
+                                                    ui::PixelFormat::Y16,
+                                                    ui::PixelFormat::YV12,
+                                                    ui::PixelFormat::DEPTH_16,
+                                                    ui::PixelFormat::DEPTH_24,
+                                                    ui::PixelFormat::DEPTH_24_STENCIL_8,
+                                                    ui::PixelFormat::DEPTH_32F,
+                                                    ui::PixelFormat::DEPTH_32F_STENCIL_8,
+                                                    ui::PixelFormat::STENCIL_8,
+                                                    ui::PixelFormat::YCBCR_P010,
+                                                    ui::PixelFormat::HSV_888};
+
+static constexpr ui::Rotation kRotations[] = {ui::Rotation::Rotation0, ui::Rotation::Rotation90,
+                                              ui::Rotation::Rotation180, ui::Rotation::Rotation270};
+
+static constexpr BnSurfaceComposer::ISurfaceComposerTag kSurfaceComposerTags[]{
+        BnSurfaceComposer::BOOT_FINISHED,
+        BnSurfaceComposer::CREATE_CONNECTION,
+        BnSurfaceComposer::GET_STATIC_DISPLAY_INFO,
+        BnSurfaceComposer::CREATE_DISPLAY_EVENT_CONNECTION,
+        BnSurfaceComposer::CREATE_DISPLAY,
+        BnSurfaceComposer::DESTROY_DISPLAY,
+        BnSurfaceComposer::GET_PHYSICAL_DISPLAY_TOKEN,
+        BnSurfaceComposer::SET_TRANSACTION_STATE,
+        BnSurfaceComposer::AUTHENTICATE_SURFACE,
+        BnSurfaceComposer::GET_SUPPORTED_FRAME_TIMESTAMPS,
+        BnSurfaceComposer::GET_DISPLAY_MODES,
+        BnSurfaceComposer::GET_ACTIVE_DISPLAY_MODE,
+        BnSurfaceComposer::GET_DISPLAY_STATE,
+        BnSurfaceComposer::CAPTURE_DISPLAY,
+        BnSurfaceComposer::CAPTURE_LAYERS,
+        BnSurfaceComposer::CLEAR_ANIMATION_FRAME_STATS,
+        BnSurfaceComposer::GET_ANIMATION_FRAME_STATS,
+        BnSurfaceComposer::SET_POWER_MODE,
+        BnSurfaceComposer::GET_DISPLAY_STATS,
+        BnSurfaceComposer::GET_HDR_CAPABILITIES,
+        BnSurfaceComposer::GET_DISPLAY_COLOR_MODES,
+        BnSurfaceComposer::GET_ACTIVE_COLOR_MODE,
+        BnSurfaceComposer::SET_ACTIVE_COLOR_MODE,
+        BnSurfaceComposer::ENABLE_VSYNC_INJECTIONS,
+        BnSurfaceComposer::INJECT_VSYNC,
+        BnSurfaceComposer::GET_LAYER_DEBUG_INFO,
+        BnSurfaceComposer::GET_COMPOSITION_PREFERENCE,
+        BnSurfaceComposer::GET_COLOR_MANAGEMENT,
+        BnSurfaceComposer::GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES,
+        BnSurfaceComposer::SET_DISPLAY_CONTENT_SAMPLING_ENABLED,
+        BnSurfaceComposer::GET_DISPLAYED_CONTENT_SAMPLE,
+        BnSurfaceComposer::GET_PROTECTED_CONTENT_SUPPORT,
+        BnSurfaceComposer::IS_WIDE_COLOR_DISPLAY,
+        BnSurfaceComposer::GET_DISPLAY_NATIVE_PRIMARIES,
+        BnSurfaceComposer::GET_PHYSICAL_DISPLAY_IDS,
+        BnSurfaceComposer::ADD_REGION_SAMPLING_LISTENER,
+        BnSurfaceComposer::REMOVE_REGION_SAMPLING_LISTENER,
+        BnSurfaceComposer::SET_DESIRED_DISPLAY_MODE_SPECS,
+        BnSurfaceComposer::GET_DESIRED_DISPLAY_MODE_SPECS,
+        BnSurfaceComposer::GET_DISPLAY_BRIGHTNESS_SUPPORT,
+        BnSurfaceComposer::SET_DISPLAY_BRIGHTNESS,
+        BnSurfaceComposer::CAPTURE_DISPLAY_BY_ID,
+        BnSurfaceComposer::NOTIFY_POWER_BOOST,
+        BnSurfaceComposer::SET_GLOBAL_SHADOW_SETTINGS,
+        BnSurfaceComposer::GET_AUTO_LOW_LATENCY_MODE_SUPPORT,
+        BnSurfaceComposer::SET_AUTO_LOW_LATENCY_MODE,
+        BnSurfaceComposer::GET_GAME_CONTENT_TYPE_SUPPORT,
+        BnSurfaceComposer::SET_GAME_CONTENT_TYPE,
+        BnSurfaceComposer::SET_FRAME_RATE,
+        BnSurfaceComposer::ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN,
+        BnSurfaceComposer::SET_FRAME_TIMELINE_INFO,
+        BnSurfaceComposer::ADD_TRANSACTION_TRACE_LISTENER,
+        BnSurfaceComposer::GET_GPU_CONTEXT_PRIORITY,
+        BnSurfaceComposer::GET_MAX_ACQUIRED_BUFFER_COUNT,
+        BnSurfaceComposer::GET_DYNAMIC_DISPLAY_INFO,
+        BnSurfaceComposer::ADD_FPS_LISTENER,
+        BnSurfaceComposer::REMOVE_FPS_LISTENER,
+        BnSurfaceComposer::OVERRIDE_HDR_TYPES,
+        BnSurfaceComposer::ADD_HDR_LAYER_INFO_LISTENER,
+        BnSurfaceComposer::REMOVE_HDR_LAYER_INFO_LISTENER,
+        BnSurfaceComposer::ON_PULL_ATOM,
+        BnSurfaceComposer::ADD_TUNNEL_MODE_ENABLED_LISTENER,
+        BnSurfaceComposer::REMOVE_TUNNEL_MODE_ENABLED_LISTENER,
+        BnSurfaceComposer::ADD_WINDOW_INFOS_LISTENER,
+        BnSurfaceComposer::REMOVE_WINDOW_INFOS_LISTENER,
+};
+
+static constexpr uint32_t kMinCode = 1000;
+static constexpr uint32_t kMaxCode = 1050;
+
+class SurfaceFlingerFuzzer {
+public:
+    SurfaceFlingerFuzzer(const uint8_t *data, size_t size) : mFdp(data, size) {
+        mFlinger = mTestableFlinger.flinger();
+    };
+    void process(const uint8_t *data, size_t size);
+
+private:
+    void setUp();
+    void invokeFlinger();
+    void setTransactionState();
+    void setInternalDisplayPrimaries();
+    void setDisplayStateLocked();
+    void onTransact(const uint8_t *data, size_t size);
+
+    FuzzedDataProvider mFdp;
+    TestableSurfaceFlinger mTestableFlinger;
+    sp<SurfaceFlinger> mFlinger = nullptr;
+};
+
+void SurfaceFlingerFuzzer::invokeFlinger() {
+    mFlinger->setSchedFifo(mFdp.ConsumeBool());
+    mFlinger->setSchedAttr(mFdp.ConsumeBool());
+    mFlinger->getServiceName();
+    mFlinger->hasSyncFramework = mFdp.ConsumeBool();
+    mFlinger->dispSyncPresentTimeOffset = mFdp.ConsumeIntegral<int64_t>();
+    mFlinger->useHwcForRgbToYuv = mFdp.ConsumeBool();
+    mFlinger->maxFrameBufferAcquiredBuffers = mFdp.ConsumeIntegral<int64_t>();
+    mFlinger->maxGraphicsWidth = mFdp.ConsumeIntegral<uint32_t>();
+    mFlinger->maxGraphicsHeight = mFdp.ConsumeIntegral<uint32_t>();
+    mFlinger->hasWideColorDisplay = mFdp.ConsumeBool();
+    mFlinger->internalDisplayOrientation = mFdp.PickValueInArray(kRotations);
+    mFlinger->useContextPriority = mFdp.ConsumeBool();
+
+    mFlinger->defaultCompositionDataspace = mFdp.PickValueInArray(kDataspaces);
+    mFlinger->defaultCompositionPixelFormat = mFdp.PickValueInArray(kPixelFormats);
+    mFlinger->wideColorGamutCompositionDataspace = mFdp.PickValueInArray(kDataspaces);
+    mFlinger->wideColorGamutCompositionPixelFormat = mFdp.PickValueInArray(kPixelFormats);
+
+    mFlinger->enableLatchUnsignaledConfig = mFdp.PickValueInArray(kLatchUnsignaledConfig);
+
+    mFlinger->scheduleComposite(mFdp.ConsumeBool()
+                                        ? scheduler::ISchedulerCallback::FrameHint::kActive
+                                        : scheduler::ISchedulerCallback::FrameHint::kNone);
+
+    mFlinger->scheduleRepaint();
+    mFlinger->scheduleSample();
+
+    uint32_t texture = mFlinger->getNewTexture();
+    mFlinger->deleteTextureAsync(texture);
+
+    sp<IBinder> handle = defaultServiceManager()->checkService(
+            String16(mFdp.ConsumeRandomLengthString().c_str()));
+    mFlinger->fromHandle(handle);
+    mFlinger->windowInfosReported();
+    mFlinger->disableExpensiveRendering();
+}
+
+void SurfaceFlingerFuzzer::setInternalDisplayPrimaries() {
+    ui::DisplayPrimaries primaries;
+    primaries.red.X = mFdp.ConsumeFloatingPoint<float>();
+    primaries.red.Y = mFdp.ConsumeFloatingPoint<float>();
+    primaries.red.Z = mFdp.ConsumeFloatingPoint<float>();
+    primaries.green.X = mFdp.ConsumeFloatingPoint<float>();
+    primaries.green.Y = mFdp.ConsumeFloatingPoint<float>();
+    primaries.green.Z = mFdp.ConsumeFloatingPoint<float>();
+    primaries.blue.X = mFdp.ConsumeFloatingPoint<float>();
+    primaries.blue.Y = mFdp.ConsumeFloatingPoint<float>();
+    primaries.blue.Z = mFdp.ConsumeFloatingPoint<float>();
+    primaries.white.X = mFdp.ConsumeFloatingPoint<float>();
+    primaries.white.Y = mFdp.ConsumeFloatingPoint<float>();
+    primaries.white.Z = mFdp.ConsumeFloatingPoint<float>();
+    mTestableFlinger.setInternalDisplayPrimaries(primaries);
+}
+
+void SurfaceFlingerFuzzer::setTransactionState() {
+    Vector<ComposerState> states;
+    Vector<DisplayState> displays;
+    ComposerState composerState;
+    composerState.state.what = layer_state_t::eLayerChanged;
+    composerState.state.surface = nullptr;
+    states.add(composerState);
+    uint32_t flags = mFdp.ConsumeIntegral<uint32_t>();
+    const sp<IBinder> applyToken = nullptr;
+    int64_t desiredPresentTime = mFdp.ConsumeIntegral<int64_t>();
+    bool isAutoTimestamp = mFdp.ConsumeBool();
+    bool hasListenerCallbacks = mFdp.ConsumeBool();
+    std::vector<ListenerCallbacks> listenerCallbacks{};
+    uint64_t transactionId = mFdp.ConsumeIntegral<uint64_t>();
+
+    mTestableFlinger.setTransactionState(FrameTimelineInfo{}, states, displays, flags, applyToken,
+                                         InputWindowCommands{}, desiredPresentTime, isAutoTimestamp,
+                                         {}, hasListenerCallbacks, listenerCallbacks,
+                                         transactionId);
+}
+
+void SurfaceFlingerFuzzer::setDisplayStateLocked() {
+    DisplayState state{};
+    mTestableFlinger.setDisplayStateLocked(state);
+}
+
+void SurfaceFlingerFuzzer::onTransact(const uint8_t *data, size_t size) {
+    Parcel fuzzedData, reply;
+    fuzzedData.writeInterfaceToken(String16("android.ui.ISurfaceComposer"));
+    fuzzedData.setData(data, size);
+    fuzzedData.setDataPosition(0);
+    uint32_t code = mFdp.ConsumeBool() ? mFdp.PickValueInArray(kSurfaceComposerTags)
+                                       : mFdp.ConsumeIntegralInRange<uint32_t>(kMinCode, kMaxCode);
+    mTestableFlinger.onTransact(code, fuzzedData, &reply, 0);
+}
+
+void SurfaceFlingerFuzzer::setUp() {
+    mTestableFlinger.setupScheduler(std::make_unique<android::mock::VsyncController>(),
+                                    std::make_unique<android::mock::VSyncTracker>(),
+                                    std::make_unique<android::mock::EventThread>(),
+                                    std::make_unique<android::mock::EventThread>());
+
+    mTestableFlinger.setupTimeStats(std::make_unique<android::mock::TimeStats>());
+
+    std::unique_ptr<android::renderengine::RenderEngine> renderEngine =
+            std::make_unique<android::renderengine::mock::RenderEngine>();
+    mTestableFlinger.setupRenderEngine(std::move(renderEngine));
+    mTestableFlinger.setupComposer(std::make_unique<android::Hwc2::mock::Composer>());
+}
+
+void SurfaceFlingerFuzzer::process(const uint8_t *data, size_t size) {
+    setUp();
+
+    invokeFlinger();
+
+    mTestableFlinger.fuzzSurfaceFlinger(data, size);
+
+    mTestableFlinger.setCreateBufferQueueFunction(
+            surfaceflinger::test::Factory::CreateBufferQueueFunction());
+    mTestableFlinger.setCreateNativeWindowSurface(
+            surfaceflinger::test::Factory::CreateNativeWindowSurfaceFunction());
+
+    setInternalDisplayPrimaries();
+
+    mTestableFlinger.enableHalVirtualDisplays(mFdp.ConsumeBool());
+
+    mTestableFlinger.commitTransactionsLocked(mFdp.ConsumeIntegral<uint32_t>());
+
+    mTestableFlinger.notifyPowerBoost(mFdp.ConsumeIntegral<int32_t>());
+
+    setDisplayStateLocked();
+
+    setTransactionState();
+    mTestableFlinger.flushTransactionQueues();
+
+    onTransact(data, size);
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    android::fuzz::SurfaceFlingerFuzzer surfaceFlingerFuzzer(data, size);
+    surfaceFlingerFuzzer.process(data, size);
+    return 0;
+}
+
+} // namespace android::fuzz
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
new file mode 100644
index 0000000..0a458c2
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -0,0 +1,803 @@
+/*
+ * Copyright 2021 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 <compositionengine/Display.h>
+#include <compositionengine/LayerFECompositionState.h>
+#include <compositionengine/OutputLayer.h>
+#include <compositionengine/impl/CompositionEngine.h>
+#include <compositionengine/impl/Display.h>
+#include <compositionengine/impl/OutputLayerCompositionState.h>
+#include <gui/LayerDebugInfo.h>
+#include <gui/ScreenCaptureResults.h>
+#include <gui/SurfaceComposerClient.h>
+#include <gui/mock/GraphicBufferProducer.h>
+#include <ui/DisplayStatInfo.h>
+#include <ui/DynamicDisplayInfo.h>
+
+#include "BufferQueueLayer.h"
+#include "BufferStateLayer.h"
+#include "ContainerLayer.h"
+#include "DisplayDevice.h"
+#include "DisplayHardware/ComposerHal.h"
+#include "EffectLayer.h"
+#include "FrameTimeline/FrameTimeline.h"
+#include "FrameTracer/FrameTracer.h"
+#include "Layer.h"
+#include "NativeWindowSurface.h"
+#include "Scheduler/EventThread.h"
+#include "Scheduler/MessageQueue.h"
+#include "Scheduler/RefreshRateConfigs.h"
+#include "Scheduler/VSyncTracker.h"
+#include "Scheduler/VsyncConfiguration.h"
+#include "Scheduler/VsyncController.h"
+#include "Scheduler/VsyncModulator.h"
+#include "StartPropertySetThread.h"
+#include "SurfaceFlinger.h"
+#include "SurfaceFlingerDefaultFactory.h"
+#include "SurfaceInterceptor.h"
+#include "TimeStats/TimeStats.h"
+
+#include "renderengine/mock/RenderEngine.h"
+#include "scheduler/TimeKeeper.h"
+#include "tests/unittests/mock/DisplayHardware/MockComposer.h"
+#include "tests/unittests/mock/DisplayHardware/MockHWC2.h"
+#include "tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h"
+#include "tests/unittests/mock/MockEventThread.h"
+#include "tests/unittests/mock/MockFrameTimeline.h"
+#include "tests/unittests/mock/MockFrameTracer.h"
+#include "tests/unittests/mock/MockNativeWindowSurface.h"
+#include "tests/unittests/mock/MockSurfaceInterceptor.h"
+#include "tests/unittests/mock/MockTimeStats.h"
+#include "tests/unittests/mock/MockVSyncTracker.h"
+#include "tests/unittests/mock/MockVsyncController.h"
+
+namespace android {
+namespace Hwc2 {
+
+class Composer;
+
+namespace types = hardware::graphics::common;
+
+namespace V2_1 = hardware::graphics::composer::V2_1;
+namespace V2_2 = hardware::graphics::composer::V2_2;
+namespace V2_3 = hardware::graphics::composer::V2_3;
+namespace V2_4 = hardware::graphics::composer::V2_4;
+
+using types::V1_0::ColorTransform;
+using types::V1_0::Transform;
+using types::V1_1::RenderIntent;
+using types::V1_2::ColorMode;
+using types::V1_2::Dataspace;
+using types::V1_2::Hdr;
+using types::V1_2::PixelFormat;
+
+using V2_1::Config;
+using V2_1::Display;
+using V2_1::Error;
+using V2_1::Layer;
+using V2_4::CommandReaderBase;
+using V2_4::CommandWriterBase;
+using V2_4::IComposer;
+using V2_4::IComposerCallback;
+using V2_4::IComposerClient;
+using V2_4::VsyncPeriodChangeTimeline;
+using V2_4::VsyncPeriodNanos;
+using DisplayCapability = IComposerClient::DisplayCapability;
+using PerFrameMetadata = IComposerClient::PerFrameMetadata;
+using PerFrameMetadataKey = IComposerClient::PerFrameMetadataKey;
+using PerFrameMetadataBlob = IComposerClient::PerFrameMetadataBlob;
+}; // namespace Hwc2
+
+static constexpr hal::HWDisplayId kHwDisplayId = 1000;
+
+static constexpr ui::Hdr kHdrTypes[] = {ui::Hdr::DOLBY_VISION, ui::Hdr::HDR10, ui::Hdr::HLG,
+                                        ui::Hdr::HDR10_PLUS};
+
+static constexpr ui::ColorMode kColormodes[] = {ui::ColorMode::NATIVE,
+                                                ui::ColorMode::STANDARD_BT601_625,
+                                                ui::ColorMode::STANDARD_BT601_625_UNADJUSTED,
+                                                ui::ColorMode::STANDARD_BT601_525,
+                                                ui::ColorMode::STANDARD_BT601_525_UNADJUSTED,
+                                                ui::ColorMode::STANDARD_BT709,
+                                                ui::ColorMode::DCI_P3,
+                                                ui::ColorMode::SRGB,
+                                                ui::ColorMode::ADOBE_RGB,
+                                                ui::ColorMode::DISPLAY_P3,
+                                                ui::ColorMode::BT2020,
+                                                ui::ColorMode::BT2100_PQ,
+                                                ui::ColorMode::BT2100_HLG,
+                                                ui::ColorMode::DISPLAY_BT2020};
+
+FloatRect getFuzzedFloatRect(FuzzedDataProvider *fdp) {
+    return FloatRect(fdp->ConsumeFloatingPoint<float>() /*left*/,
+                     fdp->ConsumeFloatingPoint<float>() /*right*/,
+                     fdp->ConsumeFloatingPoint<float>() /*top*/,
+                     fdp->ConsumeFloatingPoint<float>() /*bottom*/);
+}
+
+HdrMetadata getFuzzedHdrMetadata(FuzzedDataProvider *fdp) {
+    HdrMetadata hdrMetadata;
+    if (fdp->ConsumeBool()) {
+        hdrMetadata.cta8613.maxContentLightLevel = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.cta8613.maxFrameAverageLightLevel = fdp->ConsumeFloatingPoint<float>();
+
+        hdrMetadata.validTypes |= HdrMetadata::CTA861_3;
+    } else {
+        hdrMetadata.smpte2086.displayPrimaryRed.x = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.displayPrimaryRed.y = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.displayPrimaryGreen.x = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.displayPrimaryGreen.y = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.displayPrimaryBlue.x = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.displayPrimaryBlue.y = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.whitePoint.x = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.whitePoint.y = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.minLuminance = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.maxLuminance = fdp->ConsumeFloatingPoint<float>();
+
+        hdrMetadata.validTypes |= HdrMetadata::SMPTE2086;
+    }
+    return hdrMetadata;
+}
+
+class EventThread;
+
+namespace hal = android::hardware::graphics::composer::hal;
+
+struct FakePhaseOffsets : scheduler::VsyncConfiguration {
+    static constexpr nsecs_t FAKE_PHASE_OFFSET_NS = 0;
+    static constexpr auto FAKE_DURATION_OFFSET_NS = std::chrono::nanoseconds(0);
+
+    VsyncConfigSet getConfigsForRefreshRate(Fps) const override { return getCurrentConfigs(); }
+
+    VsyncConfigSet getCurrentConfigs() const override {
+        return {{FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS, FAKE_DURATION_OFFSET_NS,
+                 FAKE_DURATION_OFFSET_NS},
+                {FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS, FAKE_DURATION_OFFSET_NS,
+                 FAKE_DURATION_OFFSET_NS},
+                {FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS, FAKE_DURATION_OFFSET_NS,
+                 FAKE_DURATION_OFFSET_NS},
+                FAKE_DURATION_OFFSET_NS};
+    }
+
+    void reset() override {}
+    void setRefreshRateFps(Fps) override {}
+    void dump(std::string &) const override {}
+};
+namespace scheduler {
+class TestableScheduler : public Scheduler, private ICompositor {
+public:
+    TestableScheduler(const std::shared_ptr<scheduler::RefreshRateConfigs> &refreshRateConfigs,
+                      ISchedulerCallback &callback)
+          : TestableScheduler(std::make_unique<android::mock::VsyncController>(),
+                              std::make_unique<android::mock::VSyncTracker>(), refreshRateConfigs,
+                              callback) {}
+
+    void scheduleFrame(){};
+    void postMessage(sp<MessageHandler> &&){};
+
+    TestableScheduler(std::unique_ptr<VsyncController> controller,
+                      std::unique_ptr<VSyncTracker> tracker,
+                      std::shared_ptr<RefreshRateConfigs> configs, ISchedulerCallback &callback)
+          : Scheduler(*this, callback, Feature::kContentDetection) {
+        mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller)));
+        setRefreshRateConfigs(std::move(configs));
+    }
+
+    ConnectionHandle createConnection(std::unique_ptr<EventThread> eventThread) {
+        return Scheduler::createConnection(std::move(eventThread));
+    }
+
+    auto &mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; }
+    auto &mutableHWVsyncAvailable() { return mHWVsyncAvailable; }
+
+    auto &mutableLayerHistory() { return mLayerHistory; }
+
+    auto refreshRateConfigs() { return holdRefreshRateConfigs(); }
+
+    void replaceTouchTimer(int64_t millis) {
+        if (mTouchTimer) {
+            mTouchTimer.reset();
+        }
+        mTouchTimer.emplace(
+                "Testable Touch timer", std::chrono::milliseconds(millis),
+                [this] { touchTimerCallback(TimerState::Reset); },
+                [this] { touchTimerCallback(TimerState::Expired); });
+        mTouchTimer->start();
+    }
+
+    bool isTouchActive() {
+        std::lock_guard<std::mutex> lock(mPolicyLock);
+        return mPolicy.touch == Scheduler::TouchState::Active;
+    }
+
+    void dispatchCachedReportedMode() {
+        std::lock_guard<std::mutex> lock(mPolicyLock);
+        return Scheduler::dispatchCachedReportedMode();
+    }
+
+    void clearCachedReportedMode() {
+        std::lock_guard<std::mutex> lock(mPolicyLock);
+        mPolicy.cachedModeChangedParams.reset();
+    }
+
+    void onNonPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) {
+        return Scheduler::onNonPrimaryDisplayModeChanged(handle, mode);
+    }
+
+private:
+    // ICompositor overrides:
+    bool commit(nsecs_t, int64_t, nsecs_t) override { return false; }
+    void composite(nsecs_t) override {}
+    void sample() override {}
+};
+}; // namespace scheduler
+
+namespace surfaceflinger::test {
+
+class Factory final : public surfaceflinger::Factory {
+public:
+    ~Factory() = default;
+
+    std::unique_ptr<HWComposer> createHWComposer(const std::string &) override { return nullptr; }
+
+    std::unique_ptr<MessageQueue> createMessageQueue(ICompositor &compositor) {
+        return std::make_unique<android::impl::MessageQueue>(compositor);
+    }
+
+    std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
+            Fps /*currentRefreshRate*/) override {
+        return std::make_unique<FakePhaseOffsets>();
+    }
+
+    std::unique_ptr<scheduler::Scheduler> createScheduler(
+            const std::shared_ptr<scheduler::RefreshRateConfigs> &,
+            scheduler::ISchedulerCallback &) {
+        return nullptr;
+    }
+
+    sp<SurfaceInterceptor> createSurfaceInterceptor() override {
+        return new android::impl::SurfaceInterceptor();
+    }
+
+    sp<StartPropertySetThread> createStartPropertySetThread(bool timestampPropertyValue) override {
+        return new StartPropertySetThread(timestampPropertyValue);
+    }
+
+    sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs &creationArgs) override {
+        return new DisplayDevice(creationArgs);
+    }
+
+    sp<GraphicBuffer> createGraphicBuffer(uint32_t width, uint32_t height, PixelFormat format,
+                                          uint32_t layerCount, uint64_t usage,
+                                          std::string requestorName) override {
+        return new GraphicBuffer(width, height, format, layerCount, usage, requestorName);
+    }
+
+    void createBufferQueue(sp<IGraphicBufferProducer> *outProducer,
+                           sp<IGraphicBufferConsumer> *outConsumer,
+                           bool consumerIsSurfaceFlinger) override {
+        if (!mCreateBufferQueue) {
+            BufferQueue::createBufferQueue(outProducer, outConsumer, consumerIsSurfaceFlinger);
+            return;
+        }
+        mCreateBufferQueue(outProducer, outConsumer, consumerIsSurfaceFlinger);
+    }
+
+    sp<IGraphicBufferProducer> createMonitoredProducer(const sp<IGraphicBufferProducer> &producer,
+                                                       const sp<SurfaceFlinger> &flinger,
+                                                       const wp<Layer> &layer) override {
+        return new MonitoredProducer(producer, flinger, layer);
+    }
+
+    sp<BufferLayerConsumer> createBufferLayerConsumer(const sp<IGraphicBufferConsumer> &consumer,
+                                                      renderengine::RenderEngine &renderEngine,
+                                                      uint32_t textureName, Layer *layer) override {
+        return new BufferLayerConsumer(consumer, renderEngine, textureName, layer);
+    }
+
+    std::unique_ptr<surfaceflinger::NativeWindowSurface> createNativeWindowSurface(
+            const sp<IGraphicBufferProducer> &producer) override {
+        if (!mCreateNativeWindowSurface) return nullptr;
+        return mCreateNativeWindowSurface(producer);
+    }
+
+    std::unique_ptr<compositionengine::CompositionEngine> createCompositionEngine() override {
+        return compositionengine::impl::createCompositionEngine();
+    }
+
+    sp<BufferQueueLayer> createBufferQueueLayer(const LayerCreationArgs &) override {
+        return nullptr;
+    }
+
+    sp<BufferStateLayer> createBufferStateLayer(const LayerCreationArgs &) override {
+        return nullptr;
+    }
+
+    sp<EffectLayer> createEffectLayer(const LayerCreationArgs &args) override {
+        return new EffectLayer(args);
+    }
+
+    sp<ContainerLayer> createContainerLayer(const LayerCreationArgs &args) override {
+        return new ContainerLayer(args);
+    }
+
+    std::unique_ptr<FrameTracer> createFrameTracer() override {
+        return std::make_unique<android::mock::FrameTracer>();
+    }
+
+    std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
+            std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid = 0) override {
+        return std::make_unique<android::mock::FrameTimeline>(timeStats, surfaceFlingerPid);
+    }
+
+    using CreateBufferQueueFunction =
+            std::function<void(sp<IGraphicBufferProducer> * /* outProducer */,
+                               sp<IGraphicBufferConsumer> * /* outConsumer */,
+                               bool /* consumerIsSurfaceFlinger */)>;
+    CreateBufferQueueFunction mCreateBufferQueue;
+
+    using CreateNativeWindowSurfaceFunction =
+            std::function<std::unique_ptr<surfaceflinger::NativeWindowSurface>(
+                    const sp<IGraphicBufferProducer> &)>;
+    CreateNativeWindowSurfaceFunction mCreateNativeWindowSurface;
+
+    using CreateCompositionEngineFunction =
+            std::function<std::unique_ptr<compositionengine::CompositionEngine>()>;
+    CreateCompositionEngineFunction mCreateCompositionEngine;
+};
+
+} // namespace surfaceflinger::test
+
+// TODO(b/189053744) : Create a common test/mock library for surfaceflinger
+class TestableSurfaceFlinger final : private scheduler::ISchedulerCallback {
+public:
+    using HotplugEvent = SurfaceFlinger::HotplugEvent;
+
+    SurfaceFlinger *flinger() { return mFlinger.get(); }
+    scheduler::TestableScheduler *scheduler() { return mScheduler; }
+
+    // Allow reading display state without locking, as if called on the SF main thread.
+    auto onInitializeDisplays() NO_THREAD_SAFETY_ANALYSIS {
+        return mFlinger->onInitializeDisplays();
+    }
+
+    void scheduleComposite(FrameHint){};
+
+    void setGlobalShadowSettings(FuzzedDataProvider *fdp) {
+        const half4 ambientColor{fdp->ConsumeFloatingPoint<float>(),
+                                 fdp->ConsumeFloatingPoint<float>(),
+                                 fdp->ConsumeFloatingPoint<float>(),
+                                 fdp->ConsumeFloatingPoint<float>()};
+        const half4 spotColor{fdp->ConsumeFloatingPoint<float>(),
+                              fdp->ConsumeFloatingPoint<float>(),
+                              fdp->ConsumeFloatingPoint<float>(),
+                              fdp->ConsumeFloatingPoint<float>()};
+        float lightPosY = fdp->ConsumeFloatingPoint<float>();
+        float lightPosZ = fdp->ConsumeFloatingPoint<float>();
+        float lightRadius = fdp->ConsumeFloatingPoint<float>();
+        mFlinger->setGlobalShadowSettings(ambientColor, spotColor, lightPosY, lightPosZ,
+                                          lightRadius);
+    }
+
+    void onPullAtom(FuzzedDataProvider *fdp) {
+        const int32_t atomId = fdp->ConsumeIntegral<uint8_t>();
+        std::string pulledData = fdp->ConsumeRandomLengthString().c_str();
+        bool success = fdp->ConsumeBool();
+        mFlinger->onPullAtom(atomId, &pulledData, &success);
+    }
+
+    void fuzzDumpsysAndDebug(FuzzedDataProvider *fdp) {
+        std::string result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->appendSfConfigString(result);
+        result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->listLayersLocked(result);
+
+        using DumpArgs = Vector<String16>;
+        DumpArgs dumpArgs;
+        dumpArgs.push_back(String16(fdp->ConsumeRandomLengthString().c_str()));
+        mFlinger->clearStatsLocked(dumpArgs, result);
+
+        mFlinger->dumpTimeStats(dumpArgs, fdp->ConsumeBool(), result);
+        mFlinger->logFrameStats();
+
+        result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->dumpFrameTimeline(dumpArgs, result);
+
+        result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->dumpStaticScreenStats(result);
+
+        result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->dumpFrameEventsLocked(result);
+
+        result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->dumpRawDisplayIdentificationData(dumpArgs, result);
+
+        LayersProto layersProto = mFlinger->dumpDrawingStateProto(fdp->ConsumeIntegral<uint32_t>());
+        mFlinger->dumpOffscreenLayersProto(layersProto);
+        LayersTraceProto layersTraceProto{};
+        mFlinger->dumpDisplayProto(layersTraceProto);
+
+        result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->dumpHwc(result);
+
+        mFlinger->calculateColorMatrix(fdp->ConsumeFloatingPoint<float>());
+        mFlinger->updateColorMatrixLocked();
+        mFlinger->CheckTransactCodeCredentials(fdp->ConsumeIntegral<uint32_t>());
+
+        const CountDownLatch transactionCommittedSignal(fdp->ConsumeIntegral<uint32_t>());
+        mFlinger->waitForSynchronousTransaction(transactionCommittedSignal);
+        mFlinger->signalSynchronousTransactions(fdp->ConsumeIntegral<uint32_t>());
+    }
+
+    void getCompositionPreference() {
+        ui::Dataspace outDataspace;
+        ui::PixelFormat outPixelFormat;
+        ui::Dataspace outWideColorGamutDataspace;
+        ui::PixelFormat outWideColorGamutPixelFormat;
+        mFlinger->getCompositionPreference(&outDataspace, &outPixelFormat,
+                                           &outWideColorGamutDataspace,
+                                           &outWideColorGamutPixelFormat);
+    }
+
+    void overrideHdrTypes(sp<IBinder> &display, FuzzedDataProvider *fdp) {
+        std::vector<ui::Hdr> hdrTypes;
+        hdrTypes.push_back(fdp->PickValueInArray(kHdrTypes));
+        mFlinger->overrideHdrTypes(display, hdrTypes);
+    }
+
+    void getDisplayedContentSample(sp<IBinder> &display, FuzzedDataProvider *fdp) {
+        DisplayedFrameStats outDisplayedFrameStats;
+        mFlinger->getDisplayedContentSample(display, fdp->ConsumeIntegral<uint64_t>(),
+                                            fdp->ConsumeIntegral<uint64_t>(),
+                                            &outDisplayedFrameStats);
+    }
+
+    void getDisplayStats(sp<IBinder> &display) {
+        android::DisplayStatInfo stats;
+        mFlinger->getDisplayStats(display, &stats);
+    }
+
+    void getDisplayState(sp<IBinder> &display) {
+        ui::DisplayState displayState;
+        mFlinger->getDisplayState(display, &displayState);
+    }
+
+    void getStaticDisplayInfo(sp<IBinder> &display) {
+        ui::StaticDisplayInfo staticDisplayInfo;
+        mFlinger->getStaticDisplayInfo(display, &staticDisplayInfo);
+    }
+
+    void getDynamicDisplayInfo(sp<IBinder> &display) {
+        android::ui::DynamicDisplayInfo dynamicDisplayInfo;
+        mFlinger->getDynamicDisplayInfo(display, &dynamicDisplayInfo);
+    }
+    void getDisplayNativePrimaries(sp<IBinder> &display) {
+        android::ui::DisplayPrimaries displayPrimaries;
+        mFlinger->getDisplayNativePrimaries(display, displayPrimaries);
+    }
+
+    void getDesiredDisplayModeSpecs(sp<IBinder> &display) {
+        ui::DisplayModeId outDefaultMode;
+        bool outAllowGroupSwitching;
+        float outPrimaryRefreshRateMin;
+        float outPrimaryRefreshRateMax;
+        float outAppRequestRefreshRateMin;
+        float outAppRequestRefreshRateMax;
+        mFlinger->getDesiredDisplayModeSpecs(display, &outDefaultMode, &outAllowGroupSwitching,
+                                             &outPrimaryRefreshRateMin, &outPrimaryRefreshRateMax,
+                                             &outAppRequestRefreshRateMin,
+                                             &outAppRequestRefreshRateMax);
+    }
+
+    void setVsyncConfig(FuzzedDataProvider *fdp) {
+        const scheduler::VsyncModulator::VsyncConfig vsyncConfig{};
+        mFlinger->setVsyncConfig(vsyncConfig, fdp->ConsumeIntegral<nsecs_t>());
+    }
+
+    void updateCompositorTiming(FuzzedDataProvider *fdp) {
+        std::shared_ptr<FenceTime> presentFenceTime = FenceTime::NO_FENCE;
+        mFlinger->updateCompositorTiming({}, fdp->ConsumeIntegral<nsecs_t>(), presentFenceTime);
+    }
+
+    void getCompositorTiming() {
+        CompositorTiming compositorTiming;
+        mFlinger->getCompositorTiming(&compositorTiming);
+    }
+
+    sp<IBinder> fuzzBoot(FuzzedDataProvider *fdp) {
+        mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(fdp->ConsumeBool());
+        mFlinger->createConnection();
+
+        DisplayIdGenerator<HalVirtualDisplayId> kGenerator;
+        HalVirtualDisplayId halVirtualDisplayId = kGenerator.generateId().value();
+
+        ui::Size uiSize{fdp->ConsumeIntegral<int32_t>(), fdp->ConsumeIntegral<int32_t>()};
+        ui::PixelFormat pixelFormat{};
+        mFlinger->getHwComposer().allocateVirtualDisplay(halVirtualDisplayId, uiSize, &pixelFormat);
+
+        PhysicalDisplayId physicalDisplayId = SurfaceComposerClient::getInternalDisplayId().value();
+        mFlinger->getHwComposer().allocatePhysicalDisplay(kHwDisplayId, physicalDisplayId);
+
+        sp<IBinder> display =
+                mFlinger->createDisplay(String8(fdp->ConsumeRandomLengthString().c_str()),
+                                        fdp->ConsumeBool());
+
+        onInitializeDisplays();
+        mFlinger->getPhysicalDisplayToken(physicalDisplayId);
+
+        mFlinger->mStartPropertySetThread =
+                mFlinger->getFactory().createStartPropertySetThread(fdp->ConsumeBool());
+
+        mFlinger->bootFinished();
+
+        return display;
+    }
+
+    void fuzzSurfaceFlinger(const uint8_t *data, size_t size) {
+        FuzzedDataProvider mFdp(data, size);
+
+        sp<IBinder> display = fuzzBoot(&mFdp);
+
+        sp<IGraphicBufferProducer> bufferProducer = sp<mock::GraphicBufferProducer>::make();
+        mFlinger->authenticateSurfaceTexture(bufferProducer.get());
+
+        mFlinger->createDisplayEventConnection();
+
+        getDisplayStats(display);
+        getDisplayState(display);
+        getStaticDisplayInfo(display);
+        getDynamicDisplayInfo(display);
+        getDisplayNativePrimaries(display);
+
+        mFlinger->setAutoLowLatencyMode(display, mFdp.ConsumeBool());
+        mFlinger->setGameContentType(display, mFdp.ConsumeBool());
+        mFlinger->setPowerMode(display, mFdp.ConsumeIntegral<int>());
+        mFlinger->clearAnimationFrameStats();
+
+        overrideHdrTypes(display, &mFdp);
+
+        onPullAtom(&mFdp);
+
+        mFlinger->injectVSync(mFdp.ConsumeIntegral<nsecs_t>());
+
+        getCompositionPreference();
+        getDisplayedContentSample(display, &mFdp);
+        getDesiredDisplayModeSpecs(display);
+
+        bool outSupport;
+        mFlinger->getDisplayBrightnessSupport(display, &outSupport);
+
+        mFlinger->notifyPowerBoost(mFdp.ConsumeIntegral<int32_t>());
+
+        setGlobalShadowSettings(&mFdp);
+
+        mFlinger->binderDied(display);
+        mFlinger->onFirstRef();
+
+        mFlinger->commitTransactions();
+        mFlinger->updateInputFlinger();
+        mFlinger->updateCursorAsync();
+
+        setVsyncConfig(&mFdp);
+
+        mFlinger->flushTransactionQueues(0);
+
+        mFlinger->setTransactionFlags(mFdp.ConsumeIntegral<uint32_t>());
+        mFlinger->clearTransactionFlags(mFdp.ConsumeIntegral<uint32_t>());
+        mFlinger->commitOffscreenLayers();
+
+        mFlinger->frameIsEarly(mFdp.ConsumeIntegral<nsecs_t>(), mFdp.ConsumeIntegral<int64_t>());
+        mFlinger->computeLayerBounds();
+        mFlinger->startBootAnim();
+
+        mFlinger->readPersistentProperties();
+
+        mFlinger->exceedsMaxRenderTargetSize(mFdp.ConsumeIntegral<uint32_t>(),
+                                             mFdp.ConsumeIntegral<uint32_t>());
+
+        mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mFdp.ConsumeIntegral<uid_t>());
+
+        mFlinger->postComposition();
+
+        getCompositorTiming();
+
+        updateCompositorTiming(&mFdp);
+
+        mFlinger->setCompositorTimingSnapped({}, mFdp.ConsumeIntegral<nsecs_t>());
+        mFlinger->postFrame();
+        mFlinger->calculateExpectedPresentTime({});
+
+        mFlinger->enableHalVirtualDisplays(mFdp.ConsumeBool());
+
+        fuzzDumpsysAndDebug(&mFdp);
+
+        mFlinger->destroyDisplay(display);
+    }
+
+    void setupRenderEngine(std::unique_ptr<renderengine::RenderEngine> renderEngine) {
+        mFlinger->mCompositionEngine->setRenderEngine(std::move(renderEngine));
+    }
+
+    void setupComposer(std::unique_ptr<Hwc2::Composer> composer) {
+        mFlinger->mCompositionEngine->setHwComposer(
+                std::make_unique<impl::HWComposer>(std::move(composer)));
+    }
+
+    void setupTimeStats(const std::shared_ptr<TimeStats> &timeStats) {
+        mFlinger->mCompositionEngine->setTimeStats(timeStats);
+    }
+
+    // The ISchedulerCallback argument can be nullptr for a no-op implementation.
+    void setupScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController,
+                        std::unique_ptr<scheduler::VSyncTracker> vsyncTracker,
+                        std::unique_ptr<EventThread> appEventThread,
+                        std::unique_ptr<EventThread> sfEventThread,
+                        scheduler::ISchedulerCallback *callback = nullptr,
+                        bool hasMultipleModes = false) {
+        DisplayModes modes{DisplayMode::Builder(0)
+                                   .setId(DisplayModeId(0))
+                                   .setPhysicalDisplayId(PhysicalDisplayId::fromPort(0))
+                                   .setVsyncPeriod(16'666'667)
+                                   .setGroup(0)
+                                   .build()};
+
+        if (hasMultipleModes) {
+            modes.emplace_back(DisplayMode::Builder(1)
+                                       .setId(DisplayModeId(1))
+                                       .setPhysicalDisplayId(PhysicalDisplayId::fromPort(0))
+                                       .setVsyncPeriod(11'111'111)
+                                       .setGroup(0)
+                                       .build());
+        }
+
+        const auto currMode = DisplayModeId(0);
+        mRefreshRateConfigs = std::make_shared<scheduler::RefreshRateConfigs>(modes, currMode);
+        const auto currFps = mRefreshRateConfigs->getCurrentRefreshRate().getFps();
+        mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(currFps);
+        mFlinger->mVsyncModulator = sp<scheduler::VsyncModulator>::make(
+                mFlinger->mVsyncConfiguration->getCurrentConfigs());
+        mFlinger->mRefreshRateStats =
+                std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mTimeStats, currFps,
+                                                              /*powerMode=*/hal::PowerMode::OFF);
+
+        mScheduler = new scheduler::TestableScheduler(std::move(vsyncController),
+                                                      std::move(vsyncTracker), mRefreshRateConfigs,
+                                                      *(callback ?: this));
+
+        mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
+        mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
+        resetScheduler(mScheduler);
+    }
+
+    void resetScheduler(scheduler::Scheduler *scheduler) { mFlinger->mScheduler.reset(scheduler); }
+
+    scheduler::TestableScheduler &mutableScheduler() const { return *mScheduler; }
+
+    using CreateBufferQueueFunction = surfaceflinger::test::Factory::CreateBufferQueueFunction;
+    void setCreateBufferQueueFunction(CreateBufferQueueFunction f) {
+        mFactory.mCreateBufferQueue = f;
+    }
+
+    using CreateNativeWindowSurfaceFunction =
+            surfaceflinger::test::Factory::CreateNativeWindowSurfaceFunction;
+    void setCreateNativeWindowSurface(CreateNativeWindowSurfaceFunction f) {
+        mFactory.mCreateNativeWindowSurface = f;
+    }
+
+    void setInternalDisplayPrimaries(const ui::DisplayPrimaries &primaries) {
+        memcpy(&mFlinger->mInternalDisplayPrimaries, &primaries, sizeof(ui::DisplayPrimaries));
+    }
+
+    static auto &mutableLayerDrawingState(const sp<Layer> &layer) { return layer->mDrawingState; }
+
+    auto &mutableStateLock() { return mFlinger->mStateLock; }
+
+    static auto findOutputLayerForDisplay(const sp<Layer> &layer,
+                                          const sp<const DisplayDevice> &display) {
+        return layer->findOutputLayerForDisplay(display.get());
+    }
+
+    /* ------------------------------------------------------------------------
+     * Forwarding for functions being tested
+     */
+
+    void enableHalVirtualDisplays(bool enable) { mFlinger->enableHalVirtualDisplays(enable); }
+
+    auto commitTransactionsLocked(uint32_t transactionFlags) {
+        Mutex::Autolock lock(mFlinger->mStateLock);
+        return mFlinger->commitTransactionsLocked(transactionFlags);
+    }
+
+    auto setDisplayStateLocked(const DisplayState &s) {
+        Mutex::Autolock lock(mFlinger->mStateLock);
+        return mFlinger->setDisplayStateLocked(s);
+    }
+
+    auto notifyPowerBoost(int32_t boostId) { return mFlinger->notifyPowerBoost(boostId); }
+
+    // Allow reading display state without locking, as if called on the SF main thread.
+    auto setPowerModeInternal(const sp<DisplayDevice> &display,
+                              hal::PowerMode mode) NO_THREAD_SAFETY_ANALYSIS {
+        return mFlinger->setPowerModeInternal(display, mode);
+    }
+
+    auto onMessageReceived(int32_t /*what*/) { return 0; }
+
+    auto &getTransactionQueue() { return mFlinger->mTransactionQueue; }
+    auto &getPendingTransactionQueue() { return mFlinger->mPendingTransactionQueues; }
+
+    auto setTransactionState(
+            const FrameTimelineInfo &frameTimelineInfo, const Vector<ComposerState> &states,
+            const Vector<DisplayState> &displays, uint32_t flags, const sp<IBinder> &applyToken,
+            const InputWindowCommands &inputWindowCommands, int64_t desiredPresentTime,
+            bool isAutoTimestamp, const client_cache_t &uncacheBuffer, bool hasListenerCallbacks,
+            std::vector<ListenerCallbacks> &listenerCallbacks, uint64_t transactionId) {
+        return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken,
+                                             inputWindowCommands, desiredPresentTime,
+                                             isAutoTimestamp, uncacheBuffer, hasListenerCallbacks,
+                                             listenerCallbacks, transactionId);
+    }
+
+    auto flushTransactionQueues() { return mFlinger->flushTransactionQueues(0); };
+
+    auto onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) {
+        return mFlinger->onTransact(code, data, reply, flags);
+    }
+
+    auto getGPUContextPriority() { return mFlinger->getGPUContextPriority(); }
+
+    auto calculateMaxAcquiredBufferCount(Fps refreshRate,
+                                         std::chrono::nanoseconds presentLatency) const {
+        return SurfaceFlinger::calculateMaxAcquiredBufferCount(refreshRate, presentLatency);
+    }
+
+    /* Read-write access to private data to set up preconditions and assert
+     * post-conditions.
+     */
+
+    auto &mutableCurrentState() { return mFlinger->mCurrentState; }
+    auto &mutableDisplays() { return mFlinger->mDisplays; }
+    auto &mutableDrawingState() { return mFlinger->mDrawingState; }
+    auto &mutableInterceptor() { return mFlinger->mInterceptor; }
+
+    auto fromHandle(const sp<IBinder> &handle) { return mFlinger->fromHandle(handle); }
+
+    ~TestableSurfaceFlinger() {
+        mutableDisplays().clear();
+        mutableCurrentState().displays.clear();
+        mutableDrawingState().displays.clear();
+        mutableInterceptor().clear();
+        mFlinger->mScheduler.reset();
+        mFlinger->mCompositionEngine->setHwComposer(std::unique_ptr<HWComposer>());
+        mFlinger->mCompositionEngine->setRenderEngine(
+                std::unique_ptr<renderengine::RenderEngine>());
+    }
+
+private:
+    void scheduleRefresh(FrameHint) {}
+    void setVsyncEnabled(bool) override {}
+    void changeRefreshRate(const RefreshRate &, DisplayModeEvent) override {}
+    void kernelTimerChanged(bool) override {}
+    void triggerOnFrameRateOverridesChanged() {}
+
+    surfaceflinger::test::Factory mFactory;
+    sp<SurfaceFlinger> mFlinger = new SurfaceFlinger(mFactory, SurfaceFlinger::SkipInitialization);
+    scheduler::TestableScheduler *mScheduler = nullptr;
+    std::shared_ptr<scheduler::RefreshRateConfigs> mRefreshRateConfigs;
+};
+} // namespace android
diff --git a/services/surfaceflinger/layerproto/transactions.proto b/services/surfaceflinger/layerproto/transactions.proto
index 9b076bd..fcf4499 100644
--- a/services/surfaceflinger/layerproto/transactions.proto
+++ b/services/surfaceflinger/layerproto/transactions.proto
@@ -89,7 +89,6 @@
         eReleaseBufferListenerChanged = 0x00000400;
         eShadowRadiusChanged = 0x00000800;
 
-        eLayerCreated = 0x00001000;
         eBufferCropChanged = 0x00002000;
         eRelativeLayerChanged = 0x00004000;
         eReparent = 0x00008000;
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index d3be0ea..48f18b9 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -21,6 +21,24 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+filegroup {
+    name: "libsurfaceflinger_mock_sources",
+    srcs: [
+        "mock/DisplayHardware/MockComposer.cpp",
+        "mock/DisplayHardware/MockHWC2.cpp",
+        "mock/DisplayHardware/MockPowerAdvisor.cpp",
+        "mock/MockEventThread.cpp",
+        "mock/MockFrameTimeline.cpp",
+        "mock/MockFrameTracer.cpp",
+        "mock/MockNativeWindowSurface.cpp",
+        "mock/MockSurfaceInterceptor.cpp",
+        "mock/MockTimeStats.cpp",
+        "mock/MockVsyncController.cpp",
+        "mock/MockVSyncTracker.cpp",
+        "mock/system/window/MockNativeWindow.cpp",
+    ],
+}
+
 cc_test {
     name: "libsurfaceflinger_unittest",
     defaults: [
@@ -45,12 +63,12 @@
         address: true,
     },
     srcs: [
+        ":libsurfaceflinger_mock_sources",
         ":libsurfaceflinger_sources",
         "libsurfaceflinger_unittest_main.cpp",
         "CachingTest.cpp",
         "CompositionTest.cpp",
         "DispSyncSourceTest.cpp",
-        "DisplayIdentificationTest.cpp",
         "DisplayIdGeneratorTest.cpp",
         "DisplayTransactionTest.cpp",
         "DisplayDevice_GetBestColorModeTest.cpp",
@@ -102,18 +120,6 @@
         "VSyncPredictorTest.cpp",
         "VSyncReactorTest.cpp",
         "VsyncConfigurationTest.cpp",
-        "mock/DisplayHardware/MockComposer.cpp",
-        "mock/DisplayHardware/MockHWC2.cpp",
-        "mock/DisplayHardware/MockPowerAdvisor.cpp",
-        "mock/MockEventThread.cpp",
-        "mock/MockFrameTimeline.cpp",
-        "mock/MockFrameTracer.cpp",
-        "mock/MockNativeWindowSurface.cpp",
-        "mock/MockSurfaceInterceptor.cpp",
-        "mock/MockTimeStats.cpp",
-        "mock/MockVsyncController.cpp",
-        "mock/MockVSyncTracker.cpp",
-        "mock/system/window/MockNativeWindow.cpp",
     ],
     static_libs: [
         "android.hardware.common-V2-ndk",
diff --git a/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.h b/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.h
deleted file mode 100644
index 1c8e5cc..0000000
--- a/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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.
- */
-
-#pragma once
-
-#include "DisplayHardware/DisplayIdentification.h"
-
-namespace android {
-
-const DisplayIdentificationData& getInternalEdid();
-const DisplayIdentificationData& getExternalEdid();
-const DisplayIdentificationData& getExternalEedid();
-
-} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/DisplayIdentificationTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayIdentificationTestHelpers.h
new file mode 100644
index 0000000..975bc12
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/DisplayIdentificationTestHelpers.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <ui/DisplayIdentification.h>
+
+namespace android {
+
+template <size_t N>
+DisplayIdentificationData asDisplayIdentificationData(const unsigned char (&bytes)[N]) {
+    return DisplayIdentificationData(bytes, bytes + N - 1);
+}
+
+inline const DisplayIdentificationData& getInternalEdid() {
+    static constexpr unsigned char kInternalEdid[] =
+            "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\xa3\x42\x31\x00\x00\x00\x00"
+            "\x00\x15\x01\x03\x80\x1a\x10\x78\x0a\xd3\xe5\x95\x5c\x60\x90\x27"
+            "\x19\x50\x54\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"
+            "\x01\x01\x01\x01\x01\x01\x9e\x1b\x00\xa0\x50\x20\x12\x30\x10\x30"
+            "\x13\x00\x05\xa3\x10\x00\x00\x19\x00\x00\x00\x0f\x00\x00\x00\x00"
+            "\x00\x00\x00\x00\x00\x23\x87\x02\x64\x00\x00\x00\x00\xfe\x00\x53"
+            "\x41\x4d\x53\x55\x4e\x47\x0a\x20\x20\x20\x20\x20\x00\x00\x00\xfe"
+            "\x00\x31\x32\x31\x41\x54\x31\x31\x2d\x38\x30\x31\x0a\x20\x00\x45";
+    static const DisplayIdentificationData data = asDisplayIdentificationData(kInternalEdid);
+    return data;
+}
+
+inline const DisplayIdentificationData& getExternalEdid() {
+    static constexpr unsigned char kExternalEdid[] =
+            "\x00\xff\xff\xff\xff\xff\xff\x00\x22\xf0\x6c\x28\x01\x01\x01\x01"
+            "\x02\x16\x01\x04\xb5\x40\x28\x78\xe2\x8d\x85\xad\x4f\x35\xb1\x25"
+            "\x0e\x50\x54\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"
+            "\x01\x01\x01\x01\x01\x01\xe2\x68\x00\xa0\xa0\x40\x2e\x60\x30\x20"
+            "\x36\x00\x81\x90\x21\x00\x00\x1a\xbc\x1b\x00\xa0\x50\x20\x17\x30"
+            "\x30\x20\x36\x00\x81\x90\x21\x00\x00\x1a\x00\x00\x00\xfc\x00\x48"
+            "\x50\x20\x5a\x52\x33\x30\x77\x0a\x20\x20\x20\x20\x00\x00\x00\xff"
+            "\x00\x43\x4e\x34\x32\x30\x32\x31\x33\x37\x51\x0a\x20\x20\x00\x71";
+    static const DisplayIdentificationData data = asDisplayIdentificationData(kExternalEdid);
+    return data;
+}
+
+inline const DisplayIdentificationData& getExternalEedid() {
+    // Extended EDID with timing extension.
+    static constexpr unsigned char kExternalEedid[] =
+            "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\x2d\xfe\x08\x00\x00\x00\x00"
+            "\x29\x15\x01\x03\x80\x10\x09\x78\x0a\xee\x91\xa3\x54\x4c\x99\x26"
+            "\x0f\x50\x54\xbd\xef\x80\x71\x4f\x81\xc0\x81\x00\x81\x80\x95\x00"
+            "\xa9\xc0\xb3\x00\x01\x01\x02\x3a\x80\x18\x71\x38\x2d\x40\x58\x2c"
+            "\x45\x00\xa0\x5a\x00\x00\x00\x1e\x66\x21\x56\xaa\x51\x00\x1e\x30"
+            "\x46\x8f\x33\x00\xa0\x5a\x00\x00\x00\x1e\x00\x00\x00\xfd\x00\x18"
+            "\x4b\x0f\x51\x17\x00\x0a\x20\x20\x20\x20\x20\x20\x00\x00\x00\xfc"
+            "\x00\x53\x41\x4d\x53\x55\x4e\x47\x0a\x20\x20\x20\x20\x20\x01\x1d"
+            "\x02\x03\x1f\xf1\x47\x90\x04\x05\x03\x20\x22\x07\x23\x09\x07\x07"
+            "\x83\x01\x00\x00\xe2\x00\x0f\x67\x03\x0c\x00\x20\x00\xb8\x2d\x01"
+            "\x1d\x80\x18\x71\x1c\x16\x20\x58\x2c\x25\x00\xa0\x5a\x00\x00\x00"
+            "\x9e\x01\x1d\x00\x72\x51\xd0\x1e\x20\x6e\x28\x55\x00\xa0\x5a\x00"
+            "\x00\x00\x1e\x8c\x0a\xd0\x8a\x20\xe0\x2d\x10\x10\x3e\x96\x00\xa0"
+            "\x5a\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc6";
+    static const DisplayIdentificationData data = asDisplayIdentificationData(kExternalEedid);
+    return data;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index 45eceff..54b8bcb 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -22,7 +22,7 @@
 #pragma clang diagnostic ignored "-Wextra"
 
 #include <type_traits>
-#include "DisplayIdentificationTest.h"
+#include "DisplayIdentificationTestHelpers.h"
 
 #include <binder/IPCThreadState.h>
 #include <compositionengine/Display.h>
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index 765dec3..0069441 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -36,7 +36,7 @@
 #include "DisplayHardware/DisplayMode.h"
 #include "DisplayHardware/HWComposer.h"
 #include "DisplayHardware/Hal.h"
-#include "DisplayIdentificationTest.h"
+#include "DisplayIdentificationTestHelpers.h"
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/DisplayHardware/MockHWC2.h"
 
@@ -89,6 +89,7 @@
     MOCK_METHOD2(onComposerHalVsyncPeriodTimingChanged,
                  void(hal::HWDisplayId, const hal::VsyncPeriodChangeTimeline&));
     MOCK_METHOD1(onComposerHalSeamlessPossible, void(hal::HWDisplayId));
+    MOCK_METHOD1(onComposerHalVsyncIdle, void(hal::HWDisplayId));
 };
 
 struct HWComposerSetCallbackTest : testing::Test {
@@ -110,11 +111,9 @@
                             }),
                             Return(hardware::graphics::composer::V2_4::Error::NONE)));
     EXPECT_CALL(*mHal, registerCallback(_));
-    EXPECT_CALL(*mHal, isSupported(Hwc2::Composer::OptionalFeature::RefreshRateSwitching))
-            .WillOnce(Return(false));
 
     impl::HWComposer hwc{std::unique_ptr<Hwc2::Composer>(mHal)};
-    hwc.setCallback(&mCallback);
+    hwc.setCallback(mCallback);
 
     const auto& supported = hwc.getSupportedLayerGenericMetadata();
     EXPECT_EQ(2u, supported.size());
@@ -129,11 +128,9 @@
     EXPECT_CALL(*mHal, getLayerGenericMetadataKeys(_))
             .WillOnce(Return(hardware::graphics::composer::V2_4::Error::UNSUPPORTED));
     EXPECT_CALL(*mHal, registerCallback(_));
-    EXPECT_CALL(*mHal, isSupported(Hwc2::Composer::OptionalFeature::RefreshRateSwitching))
-            .WillOnce(Return(false));
 
     impl::HWComposer hwc{std::unique_ptr<Hwc2::Composer>(mHal)};
-    hwc.setCallback(&mCallback);
+    hwc.setCallback(mCallback);
 
     const auto& supported = hwc.getSupportedLayerGenericMetadata();
     EXPECT_EQ(0u, supported.size());
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 98746bc..4efcc05 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wextra"
-
 #undef LOG_TAG
 #define LOG_TAG "SchedulerUnittests"
 
@@ -33,6 +29,21 @@
 using namespace std::chrono_literals;
 
 namespace android::scheduler {
+namespace {
+
+DisplayModePtr createDisplayMode(DisplayModeId modeId, Fps refreshRate, int32_t group = 0,
+                                 ui::Size resolution = ui::Size()) {
+    return DisplayMode::Builder(hal::HWConfigId(modeId.value()))
+            .setId(modeId)
+            .setPhysicalDisplayId(PhysicalDisplayId::fromPort(0))
+            .setVsyncPeriod(static_cast<int32_t>(refreshRate.getPeriodNsecs()))
+            .setGroup(group)
+            .setHeight(resolution.height)
+            .setWidth(resolution.width)
+            .build();
+}
+
+} // namespace
 
 namespace hal = android::hardware::graphics::composer::hal;
 
@@ -40,143 +51,107 @@
 using LayerVoteType = RefreshRateConfigs::LayerVoteType;
 using LayerRequirement = RefreshRateConfigs::LayerRequirement;
 
+struct TestableRefreshRateConfigs : RefreshRateConfigs {
+    using RefreshRateConfigs::RefreshRateConfigs;
+
+    RefreshRate getMinSupportedRefreshRate() const {
+        std::lock_guard lock(mLock);
+        return *mMinSupportedRefreshRate;
+    }
+
+    RefreshRate getMaxSupportedRefreshRate() const {
+        std::lock_guard lock(mLock);
+        return *mMaxSupportedRefreshRate;
+    }
+
+    RefreshRate getMinRefreshRateByPolicy() const {
+        std::lock_guard lock(mLock);
+        return getMinRefreshRateByPolicyLocked();
+    }
+
+    const std::vector<Fps>& knownFrameRates() const { return mKnownFrameRates; }
+
+    using RefreshRateConfigs::GetBestRefreshRateCache;
+    auto& mutableGetBestRefreshRateCache() { return mGetBestRefreshRateCache; }
+
+    auto getBestRefreshRateAndSignals(const std::vector<LayerRequirement>& layers,
+                                      GlobalSignals signals) const {
+        return RefreshRateConfigs::getBestRefreshRate(layers, signals);
+    }
+
+    RefreshRate getBestRefreshRate(const std::vector<LayerRequirement>& layers = {},
+                                   GlobalSignals signals = {}) const {
+        return getBestRefreshRateAndSignals(layers, signals).first;
+    }
+};
+
 class RefreshRateConfigsTest : public testing::Test {
 protected:
-    using GetBestRefreshRateInvocation = RefreshRateConfigs::GetBestRefreshRateInvocation;
-
     RefreshRateConfigsTest();
     ~RefreshRateConfigsTest();
 
-    RefreshRate createRefreshRate(DisplayModePtr displayMode) {
+    static RefreshRate asRefreshRate(DisplayModePtr displayMode) {
         return {displayMode, RefreshRate::ConstructorTag(0)};
     }
 
-    Fps findClosestKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs, Fps frameRate) {
-        return refreshRateConfigs.findClosestKnownFrameRate(frameRate);
-    }
+    static constexpr DisplayModeId kModeId60{0};
+    static constexpr DisplayModeId kModeId90{1};
+    static constexpr DisplayModeId kModeId72{2};
+    static constexpr DisplayModeId kModeId120{3};
+    static constexpr DisplayModeId kModeId30{4};
+    static constexpr DisplayModeId kModeId25{5};
+    static constexpr DisplayModeId kModeId50{6};
+    static constexpr DisplayModeId kModeId24{7};
+    static constexpr DisplayModeId kModeId24Frac{8};
+    static constexpr DisplayModeId kModeId30Frac{9};
+    static constexpr DisplayModeId kModeId60Frac{10};
 
-    std::vector<Fps> getKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs) {
-        return refreshRateConfigs.mKnownFrameRates;
-    }
+    static inline const DisplayModePtr kMode60 = createDisplayMode(kModeId60, 60_Hz);
+    static inline const DisplayModePtr kMode60Frac = createDisplayMode(kModeId60Frac, 59.94_Hz);
+    static inline const DisplayModePtr kMode90 = createDisplayMode(kModeId90, 90_Hz);
+    static inline const DisplayModePtr kMode90_G1 = createDisplayMode(kModeId90, 90_Hz, 1);
+    static inline const DisplayModePtr kMode90_4K =
+            createDisplayMode(kModeId90, 90_Hz, 0, {3840, 2160});
+    static inline const DisplayModePtr kMode72 = createDisplayMode(kModeId72, 72_Hz);
+    static inline const DisplayModePtr kMode72_G1 = createDisplayMode(kModeId72, 72_Hz, 1);
+    static inline const DisplayModePtr kMode120 = createDisplayMode(kModeId120, 120_Hz);
+    static inline const DisplayModePtr kMode120_G1 = createDisplayMode(kModeId120, 120_Hz, 1);
+    static inline const DisplayModePtr kMode30 = createDisplayMode(kModeId30, 30_Hz);
+    static inline const DisplayModePtr kMode30_G1 = createDisplayMode(kModeId30, 30_Hz, 1);
+    static inline const DisplayModePtr kMode30Frac = createDisplayMode(kModeId30Frac, 29.97_Hz);
+    static inline const DisplayModePtr kMode25 = createDisplayMode(kModeId25, 25_Hz);
+    static inline const DisplayModePtr kMode25_G1 = createDisplayMode(kModeId25, 25_Hz, 1);
+    static inline const DisplayModePtr kMode50 = createDisplayMode(kModeId50, 50_Hz);
+    static inline const DisplayModePtr kMode24 = createDisplayMode(kModeId24, 24_Hz);
+    static inline const DisplayModePtr kMode24Frac = createDisplayMode(kModeId24Frac, 23.976_Hz);
 
-    RefreshRate getMinRefreshRateByPolicy(const RefreshRateConfigs& refreshRateConfigs) {
-        std::lock_guard lock(refreshRateConfigs.mLock);
-        return refreshRateConfigs.getMinRefreshRateByPolicyLocked();
-    }
+    // Test configurations.
+    static inline const DisplayModes kModes_60 = {kMode60};
+    static inline const DisplayModes kModes_60_90 = {kMode60, kMode90};
+    static inline const DisplayModes kModes_60_90_G1 = {kMode60, kMode90_G1};
+    static inline const DisplayModes kModes_60_90_4K = {kMode60, kMode90_4K};
+    static inline const DisplayModes kModes_60_72_90 = {kMode60, kMode90, kMode72};
+    static inline const DisplayModes kModes_60_90_72_120 = {kMode60, kMode90, kMode72, kMode120};
+    static inline const DisplayModes kModes_30_60_72_90_120 = {kMode60, kMode90, kMode72, kMode120,
+                                                               kMode30};
 
-    RefreshRate getMinSupportedRefreshRate(const RefreshRateConfigs& refreshRateConfigs) {
-        std::lock_guard lock(refreshRateConfigs.mLock);
-        return *refreshRateConfigs.mMinSupportedRefreshRate;
-    }
-
-    RefreshRate getMaxSupportedRefreshRate(const RefreshRateConfigs& refreshRateConfigs) {
-        std::lock_guard lock(refreshRateConfigs.mLock);
-        return *refreshRateConfigs.mMaxSupportedRefreshRate;
-    }
-
-    void setLastBestRefreshRateInvocation(RefreshRateConfigs& refreshRateConfigs,
-                                          const GetBestRefreshRateInvocation& invocation) {
-        std::lock_guard lock(refreshRateConfigs.mLock);
-        refreshRateConfigs.lastBestRefreshRateInvocation.emplace(
-                GetBestRefreshRateInvocation(invocation));
-    }
-
-    std::optional<GetBestRefreshRateInvocation> getLastBestRefreshRateInvocation(
-            const RefreshRateConfigs& refreshRateConfigs) {
-        std::lock_guard lock(refreshRateConfigs.mLock);
-        return refreshRateConfigs.lastBestRefreshRateInvocation;
-    }
-
-    // Test config IDs
-    static inline const DisplayModeId HWC_CONFIG_ID_60 = DisplayModeId(0);
-    static inline const DisplayModeId HWC_CONFIG_ID_90 = DisplayModeId(1);
-    static inline const DisplayModeId HWC_CONFIG_ID_72 = DisplayModeId(2);
-    static inline const DisplayModeId HWC_CONFIG_ID_120 = DisplayModeId(3);
-    static inline const DisplayModeId HWC_CONFIG_ID_30 = DisplayModeId(4);
-    static inline const DisplayModeId HWC_CONFIG_ID_25 = DisplayModeId(5);
-    static inline const DisplayModeId HWC_CONFIG_ID_50 = DisplayModeId(6);
-    static inline const DisplayModeId HWC_CONFIG_ID_24 = DisplayModeId(7);
-    static inline const DisplayModeId HWC_CONFIG_ID_24_FRAC = DisplayModeId(8);
-    static inline const DisplayModeId HWC_CONFIG_ID_30_FRAC = DisplayModeId(9);
-    static inline const DisplayModeId HWC_CONFIG_ID_60_FRAC = DisplayModeId(10);
-
-    // Test configs
-    DisplayModePtr mConfig60 = createDisplayMode(HWC_CONFIG_ID_60, 0, (60_Hz).getPeriodNsecs());
-    DisplayModePtr mConfig60Frac =
-            createDisplayMode(HWC_CONFIG_ID_60_FRAC, 0, (59.94_Hz).getPeriodNsecs());
-    DisplayModePtr mConfig90 = createDisplayMode(HWC_CONFIG_ID_90, 0, (90_Hz).getPeriodNsecs());
-    DisplayModePtr mConfig90DifferentGroup =
-            createDisplayMode(HWC_CONFIG_ID_90, 1, (90_Hz).getPeriodNsecs());
-    DisplayModePtr mConfig90DifferentResolution =
-            createDisplayMode(HWC_CONFIG_ID_90, 0, (90_Hz).getPeriodNsecs(), ui::Size(111, 222));
-    DisplayModePtr mConfig72 = createDisplayMode(HWC_CONFIG_ID_72, 0, (72_Hz).getPeriodNsecs());
-    DisplayModePtr mConfig72DifferentGroup =
-            createDisplayMode(HWC_CONFIG_ID_72, 1, (72_Hz).getPeriodNsecs());
-    DisplayModePtr mConfig120 = createDisplayMode(HWC_CONFIG_ID_120, 0, (120_Hz).getPeriodNsecs());
-    DisplayModePtr mConfig120DifferentGroup =
-            createDisplayMode(HWC_CONFIG_ID_120, 1, (120_Hz).getPeriodNsecs());
-    DisplayModePtr mConfig30 = createDisplayMode(HWC_CONFIG_ID_30, 0, (30_Hz).getPeriodNsecs());
-    DisplayModePtr mConfig30DifferentGroup =
-            createDisplayMode(HWC_CONFIG_ID_30, 1, (30_Hz).getPeriodNsecs());
-    DisplayModePtr mConfig30Frac =
-            createDisplayMode(HWC_CONFIG_ID_30_FRAC, 0, (29.97_Hz).getPeriodNsecs());
-    DisplayModePtr mConfig25 = createDisplayMode(HWC_CONFIG_ID_25, 0, (25_Hz).getPeriodNsecs());
-    DisplayModePtr mConfig25DifferentGroup =
-            createDisplayMode(HWC_CONFIG_ID_25, 1, (25_Hz).getPeriodNsecs());
-    DisplayModePtr mConfig50 = createDisplayMode(HWC_CONFIG_ID_50, 0, (50_Hz).getPeriodNsecs());
-    DisplayModePtr mConfig24 = createDisplayMode(HWC_CONFIG_ID_24, 0, (24_Hz).getPeriodNsecs());
-    DisplayModePtr mConfig24Frac =
-            createDisplayMode(HWC_CONFIG_ID_24_FRAC, 0, (23.976_Hz).getPeriodNsecs());
-
-    // Test device configurations
-    // The positions of the configs in the arrays below MUST match their IDs. For example,
-    // the first config should always be 60Hz, the second 90Hz etc.
-    DisplayModes m60OnlyConfigDevice = {mConfig60};
-    DisplayModes m60_90Device = {mConfig60, mConfig90};
-    DisplayModes m60_90DeviceWithDifferentGroups = {mConfig60, mConfig90DifferentGroup};
-    DisplayModes m60_90DeviceWithDifferentResolutions = {mConfig60, mConfig90DifferentResolution};
-    DisplayModes m60_72_90Device = {mConfig60, mConfig90, mConfig72};
-    DisplayModes m60_90_72_120Device = {mConfig60, mConfig90, mConfig72, mConfig120};
-    DisplayModes m30_60_72_90_120Device = {mConfig60, mConfig90, mConfig72, mConfig120, mConfig30};
-    DisplayModes m30_60Device = {mConfig60, mConfig90DifferentGroup, mConfig72DifferentGroup,
-                                 mConfig120DifferentGroup, mConfig30};
-    DisplayModes m30_60_72_90Device = {mConfig60, mConfig90, mConfig72, mConfig120DifferentGroup,
-                                       mConfig30};
-    DisplayModes m30_60_90Device = {mConfig60, mConfig90, mConfig72DifferentGroup,
-                                    mConfig120DifferentGroup, mConfig30};
-    DisplayModes m25_30_50_60Device = {mConfig60,
-                                       mConfig90,
-                                       mConfig72DifferentGroup,
-                                       mConfig120DifferentGroup,
-                                       mConfig30DifferentGroup,
-                                       mConfig25DifferentGroup,
-                                       mConfig50};
-    DisplayModes m60_120Device = {mConfig60, mConfig120};
+    static inline const DisplayModes kModes_30_60 = {kMode60, kMode90_G1, kMode72_G1, kMode120_G1,
+                                                     kMode30};
+    static inline const DisplayModes kModes_30_60_72_90 = {kMode60, kMode90, kMode72, kMode120_G1,
+                                                           kMode30};
+    static inline const DisplayModes kModes_30_60_90 = {kMode60, kMode90, kMode72_G1, kMode120_G1,
+                                                        kMode30};
+    static inline const DisplayModes kModes_25_30_50_60 = {kMode60,     kMode90,    kMode72_G1,
+                                                           kMode120_G1, kMode30_G1, kMode25_G1,
+                                                           kMode50};
+    static inline const DisplayModes kModes_60_120 = {kMode60, kMode120};
 
     // This is a typical TV configuration.
-    DisplayModes m24_25_30_50_60WithFracDevice = {mConfig24, mConfig24Frac, mConfig25,
-                                                  mConfig30, mConfig30Frac, mConfig50,
-                                                  mConfig60, mConfig60Frac};
-
-    // Expected RefreshRate objects
-    RefreshRate mExpected60Config = {mConfig60, RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpectedAlmost60Config = {createDisplayMode(HWC_CONFIG_ID_60, 0, 16666665),
-                                           RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected90Config = {mConfig90, RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected90DifferentGroupConfig = {mConfig90DifferentGroup,
-                                                   RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected90DifferentResolutionConfig = {mConfig90DifferentResolution,
-                                                        RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected72Config = {mConfig72, RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected30Config = {mConfig30, RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected120Config = {mConfig120, RefreshRate::ConstructorTag(0)};
-
-    DisplayModePtr createDisplayMode(DisplayModeId modeId, int32_t group, int64_t vsyncPeriod,
-                                     ui::Size resolution = ui::Size());
+    static inline const DisplayModes kModes_24_25_30_50_60_Frac = {kMode24, kMode24Frac, kMode25,
+                                                                   kMode30, kMode30Frac, kMode50,
+                                                                   kMode60, kMode60Frac};
 };
 
-using Builder = DisplayMode::Builder;
-
 RefreshRateConfigsTest::RefreshRateConfigsTest() {
     const ::testing::TestInfo* const test_info =
             ::testing::UnitTest::GetInstance()->current_test_info();
@@ -189,348 +164,315 @@
     ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
 }
 
-DisplayModePtr RefreshRateConfigsTest::createDisplayMode(DisplayModeId modeId, int32_t group,
-                                                         int64_t vsyncPeriod, ui::Size resolution) {
-    return DisplayMode::Builder(hal::HWConfigId(modeId.value()))
-            .setId(modeId)
-            .setPhysicalDisplayId(PhysicalDisplayId::fromPort(0))
-            .setVsyncPeriod(int32_t(vsyncPeriod))
-            .setGroup(group)
-            .setHeight(resolution.height)
-            .setWidth(resolution.width)
-            .build();
-}
-
 namespace {
 
-TEST_F(RefreshRateConfigsTest, oneDeviceConfig_SwitchingSupported) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60OnlyConfigDevice,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+TEST_F(RefreshRateConfigsTest, oneMode_canSwitch) {
+    RefreshRateConfigs configs(kModes_60, kModeId60);
+    EXPECT_FALSE(configs.canSwitch());
 }
 
 TEST_F(RefreshRateConfigsTest, invalidPolicy) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60OnlyConfigDevice,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
-    ASSERT_LT(refreshRateConfigs->setDisplayManagerPolicy({DisplayModeId(10), {60_Hz, 60_Hz}}), 0);
-    ASSERT_LT(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {20_Hz, 40_Hz}}), 0);
+    RefreshRateConfigs configs(kModes_60, kModeId60);
+    EXPECT_LT(configs.setDisplayManagerPolicy({DisplayModeId(10), {60_Hz, 60_Hz}}), 0);
+    EXPECT_LT(configs.setDisplayManagerPolicy({kModeId60, {20_Hz, 40_Hz}}), 0);
 }
 
-TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_storesFullRefreshRateMap) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+TEST_F(RefreshRateConfigsTest, twoModes_storesFullRefreshRateMap) {
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
-    const auto& minRate = getMinSupportedRefreshRate(*refreshRateConfigs);
-    const auto& performanceRate = getMaxSupportedRefreshRate(*refreshRateConfigs);
+    const auto minRate = configs.getMinSupportedRefreshRate();
+    const auto performanceRate = configs.getMaxSupportedRefreshRate();
 
-    ASSERT_EQ(mExpected60Config, minRate);
-    ASSERT_EQ(mExpected90Config, performanceRate);
+    EXPECT_EQ(asRefreshRate(kMode60), minRate);
+    EXPECT_EQ(asRefreshRate(kMode90), performanceRate);
 
-    const auto& minRateByPolicy = getMinRefreshRateByPolicy(*refreshRateConfigs);
-    const auto& performanceRateByPolicy = refreshRateConfigs->getMaxRefreshRateByPolicy();
-    ASSERT_EQ(minRateByPolicy, minRate);
-    ASSERT_EQ(performanceRateByPolicy, performanceRate);
+    const auto minRateByPolicy = configs.getMinRefreshRateByPolicy();
+    const auto performanceRateByPolicy = configs.getMaxRefreshRateByPolicy();
+
+    EXPECT_EQ(minRateByPolicy, minRate);
+    EXPECT_EQ(performanceRateByPolicy, performanceRate);
 }
 
-TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_storesFullRefreshRateMap_differentGroups) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+TEST_F(RefreshRateConfigsTest, twoModes_storesFullRefreshRateMap_differentGroups) {
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
 
-    const auto& minRate = getMinRefreshRateByPolicy(*refreshRateConfigs);
-    const auto& performanceRate = getMaxSupportedRefreshRate(*refreshRateConfigs);
-    const auto& minRate60 = getMinRefreshRateByPolicy(*refreshRateConfigs);
-    const auto& performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
+    const auto minRate = configs.getMinRefreshRateByPolicy();
+    const auto performanceRate = configs.getMaxSupportedRefreshRate();
+    const auto minRate60 = configs.getMinRefreshRateByPolicy();
+    const auto performanceRate60 = configs.getMaxRefreshRateByPolicy();
 
-    ASSERT_EQ(mExpected60Config, minRate);
-    ASSERT_EQ(mExpected60Config, minRate60);
-    ASSERT_EQ(mExpected60Config, performanceRate60);
+    EXPECT_EQ(asRefreshRate(kMode60), minRate);
+    EXPECT_EQ(asRefreshRate(kMode60), minRate60);
+    EXPECT_EQ(asRefreshRate(kMode60), performanceRate60);
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {60_Hz, 90_Hz}}), 0);
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}), 0);
+    configs.setCurrentModeId(kModeId90);
 
-    const auto& minRate90 = getMinRefreshRateByPolicy(*refreshRateConfigs);
-    const auto& performanceRate90 = refreshRateConfigs->getMaxRefreshRateByPolicy();
+    const auto minRate90 = configs.getMinRefreshRateByPolicy();
+    const auto performanceRate90 = configs.getMaxRefreshRateByPolicy();
 
-    ASSERT_EQ(mExpected90DifferentGroupConfig, performanceRate);
-    ASSERT_EQ(mExpected90DifferentGroupConfig, minRate90);
-    ASSERT_EQ(mExpected90DifferentGroupConfig, performanceRate90);
+    EXPECT_EQ(asRefreshRate(kMode90_G1), performanceRate);
+    EXPECT_EQ(asRefreshRate(kMode90_G1), minRate90);
+    EXPECT_EQ(asRefreshRate(kMode90_G1), performanceRate90);
 }
 
-TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_storesFullRefreshRateMap_differentResolutions) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentResolutions,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+TEST_F(RefreshRateConfigsTest, twoModes_storesFullRefreshRateMap_differentResolutions) {
+    TestableRefreshRateConfigs configs(kModes_60_90_4K, kModeId60);
 
-    const auto& minRate = getMinRefreshRateByPolicy(*refreshRateConfigs);
-    const auto& performanceRate = getMaxSupportedRefreshRate(*refreshRateConfigs);
-    const auto& minRate60 = getMinRefreshRateByPolicy(*refreshRateConfigs);
-    const auto& performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
+    const auto minRate = configs.getMinRefreshRateByPolicy();
+    const auto performanceRate = configs.getMaxSupportedRefreshRate();
+    const auto minRate60 = configs.getMinRefreshRateByPolicy();
+    const auto performanceRate60 = configs.getMaxRefreshRateByPolicy();
 
-    ASSERT_EQ(mExpected60Config, minRate);
-    ASSERT_EQ(mExpected60Config, minRate60);
-    ASSERT_EQ(mExpected60Config, performanceRate60);
+    EXPECT_EQ(asRefreshRate(kMode60), minRate);
+    EXPECT_EQ(asRefreshRate(kMode60), minRate60);
+    EXPECT_EQ(asRefreshRate(kMode60), performanceRate60);
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {60_Hz, 90_Hz}}), 0);
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}), 0);
+    configs.setCurrentModeId(kModeId90);
 
-    const auto& minRate90 = getMinRefreshRateByPolicy(*refreshRateConfigs);
-    const auto& performanceRate90 = refreshRateConfigs->getMaxRefreshRateByPolicy();
+    const auto minRate90 = configs.getMinRefreshRateByPolicy();
+    const auto performanceRate90 = configs.getMaxRefreshRateByPolicy();
 
-    ASSERT_EQ(mExpected90DifferentResolutionConfig, performanceRate);
-    ASSERT_EQ(mExpected90DifferentResolutionConfig, minRate90);
-    ASSERT_EQ(mExpected90DifferentResolutionConfig, performanceRate90);
+    EXPECT_EQ(asRefreshRate(kMode90_4K), performanceRate);
+    EXPECT_EQ(asRefreshRate(kMode90_4K), minRate90);
+    EXPECT_EQ(asRefreshRate(kMode90_4K), performanceRate90);
 }
 
-TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_policyChange) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+TEST_F(RefreshRateConfigsTest, twoModes_policyChange) {
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
-    auto minRate = getMinRefreshRateByPolicy(*refreshRateConfigs);
-    auto performanceRate = refreshRateConfigs->getMaxRefreshRateByPolicy();
+    const auto minRate = configs.getMinRefreshRateByPolicy();
+    const auto performanceRate = configs.getMaxRefreshRateByPolicy();
 
-    ASSERT_EQ(mExpected60Config, minRate);
-    ASSERT_EQ(mExpected90Config, performanceRate);
+    EXPECT_EQ(asRefreshRate(kMode60), minRate);
+    EXPECT_EQ(asRefreshRate(kMode90), performanceRate);
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60_Hz, 60_Hz}}), 0);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), 0);
 
-    auto minRate60 = getMinRefreshRateByPolicy(*refreshRateConfigs);
-    auto performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
-    ASSERT_EQ(mExpected60Config, minRate60);
-    ASSERT_EQ(mExpected60Config, performanceRate60);
+    const auto minRate60 = configs.getMinRefreshRateByPolicy();
+    const auto performanceRate60 = configs.getMaxRefreshRateByPolicy();
+
+    EXPECT_EQ(asRefreshRate(kMode60), minRate60);
+    EXPECT_EQ(asRefreshRate(kMode60), performanceRate60);
 }
 
-TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getCurrentRefreshRate) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+TEST_F(RefreshRateConfigsTest, twoModes_getCurrentRefreshRate) {
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
     {
-        auto current = refreshRateConfigs->getCurrentRefreshRate();
-        EXPECT_EQ(current.getModeId(), HWC_CONFIG_ID_60);
+        const auto current = configs.getCurrentRefreshRate();
+        EXPECT_EQ(current.getModeId(), kModeId60);
     }
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
+    configs.setCurrentModeId(kModeId90);
     {
-        auto current = refreshRateConfigs->getCurrentRefreshRate();
-        EXPECT_EQ(current.getModeId(), HWC_CONFIG_ID_90);
+        const auto current = configs.getCurrentRefreshRate();
+        EXPECT_EQ(current.getModeId(), kModeId90);
     }
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {90_Hz, 90_Hz}}), 0);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}), 0);
     {
-        auto current = refreshRateConfigs->getCurrentRefreshRate();
-        EXPECT_EQ(current.getModeId(), HWC_CONFIG_ID_90);
+        const auto current = configs.getCurrentRefreshRate();
+        EXPECT_EQ(current.getModeId(), kModeId90);
     }
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_noLayers) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_72_90Device, /*currentConfigId=*/
-                                                 HWC_CONFIG_ID_72);
+    {
+        TestableRefreshRateConfigs configs(kModes_60_72_90, kModeId72);
 
-    // If there are no layers we select the default frame rate, which is the max of the primary
-    // range.
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate({}, {}));
+        // If there are no layers we select the default frame rate, which is the max of the primary
+        // range.
+        EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate());
 
-    ASSERT_EQ(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60_Hz, 60_Hz}}),
-              NO_ERROR);
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate({}, {}));
-
-    // We select max even when this will cause a non-seamless switch.
-    refreshRateConfigs = std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                              /*currentConfigId=*/HWC_CONFIG_ID_60);
-    ASSERT_EQ(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_90, /*allowGroupSwitching*/ true, {0_Hz, 90_Hz}}),
-              NO_ERROR);
-    EXPECT_EQ(mExpected90DifferentGroupConfig, refreshRateConfigs->getBestRefreshRate({}, {}));
+        EXPECT_EQ(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), NO_ERROR);
+        EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate());
+    }
+    {
+        // We select max even when this will cause a non-seamless switch.
+        TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
+        constexpr bool kAllowGroupSwitching = true;
+        EXPECT_EQ(configs.setDisplayManagerPolicy({kModeId90, kAllowGroupSwitching, {0_Hz, 90_Hz}}),
+                  NO_ERROR);
+        EXPECT_EQ(asRefreshRate(kMode90_G1), configs.getBestRefreshRate());
+    }
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_60_90) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::Min;
     lr.name = "Min";
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Max;
     lr.name = "Max";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
     lr.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 45_Hz;
     lr.name = "45Hz Heuristic";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 30_Hz;
     lr.name = "30Hz Heuristic";
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 24_Hz;
     lr.name = "24Hz Heuristic";
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.name = "";
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60_Hz, 60_Hz}}), 0);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), 0);
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {90_Hz, 90_Hz}}), 0);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}), 0);
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {0_Hz, 120_Hz}}), 0);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {0_Hz, 120_Hz}}), 0);
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_multipleThreshold_60_90) {
-    RefreshRateConfigs::Config config = {.frameRateMultipleThreshold = 90};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60, config);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60, {.frameRateMultipleThreshold = 90});
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::Min;
     lr.name = "Min";
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Max;
     lr.name = "Max";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
     lr.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 45_Hz;
     lr.name = "45Hz Heuristic";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 30_Hz;
     lr.name = "30Hz Heuristic";
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 24_Hz;
     lr.name = "24Hz Heuristic";
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_60_72_90) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_72_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_72_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(mExpected72Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode72), configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_72_90_120) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
@@ -540,25 +482,23 @@
     lr1.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(mExpected120Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode120), configs.getBestRefreshRate(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 48_Hz;
     lr2.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(mExpected72Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode72), configs.getBestRefreshRate(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 48_Hz;
     lr2.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(mExpected72Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode72), configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
@@ -570,7 +510,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(mExpected120Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode120), configs.getBestRefreshRate(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -578,7 +518,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(mExpected120Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode120), configs.getBestRefreshRate(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -586,7 +526,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "60Hz ExplicitDefault";
-    EXPECT_EQ(mExpected120Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode120), configs.getBestRefreshRate(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -594,7 +534,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -602,7 +542,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected72Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode72), configs.getBestRefreshRate(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -610,7 +550,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
@@ -618,7 +558,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(mExpected72Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode72), configs.getBestRefreshRate(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -626,7 +566,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(mExpected72Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode72), configs.getBestRefreshRate(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -634,14 +574,12 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.name = "90Hz ExplicitExactOrMultiple";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_multipleThreshold) {
-    RefreshRateConfigs::Config config = {.frameRateMultipleThreshold = 120};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60, config);
+    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60,
+                                       {.frameRateMultipleThreshold = 120});
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
@@ -653,7 +591,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(mExpected120Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode120), configs.getBestRefreshRate(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -661,7 +599,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -669,7 +607,7 @@
     lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "60Hz ExplicitDefault";
-    EXPECT_EQ(mExpected72Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode72), configs.getBestRefreshRate(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -677,7 +615,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -685,7 +623,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected72Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode72), configs.getBestRefreshRate(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -693,7 +631,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
@@ -701,7 +639,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(mExpected72Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode72), configs.getBestRefreshRate(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -709,7 +647,7 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(mExpected72Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode72), configs.getBestRefreshRate(layers));
 
     lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
@@ -717,92 +655,86 @@
     lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.name = "90Hz ExplicitExactOrMultiple";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_30_60, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(mExpected30Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode30), configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(mExpected30Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode30), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_72_90) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_30_60_72_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::Min;
     lr.name = "Min";
-    EXPECT_EQ(mExpected30Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode30), configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Max;
     lr.name = "Max";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
     lr.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers, {.touch = true}));
 
     lr.desiredRefreshRate = 45_Hz;
     lr.name = "45Hz Heuristic";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers, {.touch = true}));
 
     lr.desiredRefreshRate = 30_Hz;
     lr.name = "30Hz Heuristic";
-    EXPECT_EQ(mExpected30Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(asRefreshRate(kMode30), configs.getBestRefreshRate(layers));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers, {.touch = true}));
 
     lr.desiredRefreshRate = 24_Hz;
     lr.name = "24Hz Heuristic";
-    EXPECT_EQ(mExpected72Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(asRefreshRate(kMode72), configs.getBestRefreshRate(layers));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers, {.touch = true}));
 
     lr.desiredRefreshRate = 24_Hz;
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr.name = "24Hz ExplicitExactOrMultiple";
-    EXPECT_EQ(mExpected72Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(asRefreshRate(kMode72), configs.getBestRefreshRate(layers));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers, {.touch = true}));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_PriorityTest) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
@@ -810,45 +742,43 @@
 
     lr1.vote = LayerVoteType::Min;
     lr2.vote = LayerVoteType::Max;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::Min;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::Min;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::Max;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::Max;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::Heuristic;
     lr1.desiredRefreshRate = 15_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::Heuristic;
     lr1.desiredRefreshRate = 30_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_24FpsVideo) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
@@ -856,17 +786,15 @@
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
         lr.desiredRefreshRate = Fps::fromValue(fps);
-        const auto refreshRate = refreshRateConfigs->getBestRefreshRate(layers, {});
-        EXPECT_EQ(mExpected60Config, refreshRate)
+        const auto refreshRate = configs.getBestRefreshRate(layers);
+        EXPECT_EQ(asRefreshRate(kMode60), refreshRate)
                 << lr.desiredRefreshRate << " chooses " << refreshRate.getName();
     }
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_24FpsVideo_multipleThreshold_60_120) {
-    RefreshRateConfigs::Config config = {.frameRateMultipleThreshold = 120};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60, config);
+    TestableRefreshRateConfigs configs(kModes_60_120, kModeId60,
+                                       {.frameRateMultipleThreshold = 120});
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
@@ -874,16 +802,14 @@
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
         lr.desiredRefreshRate = Fps::fromValue(fps);
-        const auto refreshRate = refreshRateConfigs->getBestRefreshRate(layers, {});
-        EXPECT_EQ(mExpected60Config, refreshRate)
+        const auto refreshRate = configs.getBestRefreshRate(layers);
+        EXPECT_EQ(asRefreshRate(kMode60), refreshRate)
                 << lr.desiredRefreshRate << " chooses " << refreshRate.getName();
     }
 }
 
-TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getBestRefreshRate_Explicit) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+TEST_F(RefreshRateConfigsTest, twoModes_getBestRefreshRate_Explicit) {
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
@@ -893,33 +819,34 @@
     lr1.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 90_Hz;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::ExplicitDefault;
     lr1.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::Heuristic;
     lr1.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, testInPolicy) {
-    ASSERT_TRUE(mExpectedAlmost60Config.inPolicy(60.000004_Hz, 60.000004_Hz));
-    ASSERT_TRUE(mExpectedAlmost60Config.inPolicy(59_Hz, 60.1_Hz));
-    ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(75_Hz, 90_Hz));
-    ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(60.0011_Hz, 90_Hz));
-    ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(50_Hz, 59.998_Hz));
+    const auto refreshRate =
+            asRefreshRate(createDisplayMode(kModeId60, Fps::fromPeriodNsecs(16'666'665)));
+
+    EXPECT_TRUE(refreshRate.inPolicy(60.000004_Hz, 60.000004_Hz));
+    EXPECT_TRUE(refreshRate.inPolicy(59_Hz, 60.1_Hz));
+    EXPECT_FALSE(refreshRate.inPolicy(75_Hz, 90_Hz));
+    EXPECT_FALSE(refreshRate.inPolicy(60.0011_Hz, 90_Hz));
+    EXPECT_FALSE(refreshRate.inPolicy(50_Hz, 59.998_Hz));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_75HzContent) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
@@ -927,16 +854,14 @@
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     for (float fps = 75.0f; fps < 100.0f; fps += 0.1f) {
         lr.desiredRefreshRate = Fps::fromValue(fps);
-        const auto refreshRate = refreshRateConfigs->getBestRefreshRate(layers, {});
-        EXPECT_EQ(mExpected90Config, refreshRate)
+        const auto refreshRate = configs.getBestRefreshRate(layers, {});
+        EXPECT_EQ(asRefreshRate(kMode90), refreshRate)
                 << lr.desiredRefreshRate << " chooses " << refreshRate.getName();
     }
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_Multiples) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
@@ -948,7 +873,7 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
@@ -956,14 +881,14 @@
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 30_Hz;
@@ -971,20 +896,18 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 30_Hz;
     lr1.name = "30Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, scrollWhileWatching60fps_60_90) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
@@ -995,28 +918,28 @@
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::NoVote;
     lr2.name = "NoVote";
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::NoVote;
     lr2.name = "NoVote";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers, {.touch = true}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers, {.touch = true}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     // The other layer starts to provide buffers
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
@@ -1025,20 +948,17 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, touchConsidered) {
-    RefreshRateConfigs::GlobalSignals consideredSignals;
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    RefreshRateConfigs configs(kModes_60_90, kModeId60);
 
-    refreshRateConfigs->getBestRefreshRate({}, {}, &consideredSignals);
-    EXPECT_EQ(false, consideredSignals.touch);
+    auto [_, signals] = configs.getBestRefreshRate({}, {});
+    EXPECT_FALSE(signals.touch);
 
-    refreshRateConfigs->getBestRefreshRate({}, {.touch = true}, &consideredSignals);
-    EXPECT_EQ(true, consideredSignals.touch);
+    std::tie(std::ignore, signals) = configs.getBestRefreshRate({}, {.touch = true});
+    EXPECT_TRUE(signals.touch);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
@@ -1050,8 +970,8 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
     lr2.name = "60Hz Heuristic";
-    refreshRateConfigs->getBestRefreshRate(layers, {.touch = true}, &consideredSignals);
-    EXPECT_EQ(true, consideredSignals.touch);
+    std::tie(std::ignore, signals) = configs.getBestRefreshRate(layers, {.touch = true});
+    EXPECT_TRUE(signals.touch);
 
     lr1.vote = LayerVoteType::ExplicitDefault;
     lr1.desiredRefreshRate = 60_Hz;
@@ -1059,8 +979,8 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
     lr2.name = "60Hz Heuristic";
-    refreshRateConfigs->getBestRefreshRate(layers, {.touch = true}, &consideredSignals);
-    EXPECT_EQ(false, consideredSignals.touch);
+    std::tie(std::ignore, signals) = configs.getBestRefreshRate(layers, {.touch = true});
+    EXPECT_FALSE(signals.touch);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.desiredRefreshRate = 60_Hz;
@@ -1068,8 +988,8 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
     lr2.name = "60Hz Heuristic";
-    refreshRateConfigs->getBestRefreshRate(layers, {.touch = true}, &consideredSignals);
-    EXPECT_EQ(true, consideredSignals.touch);
+    std::tie(std::ignore, signals) = configs.getBestRefreshRate(layers, {.touch = true});
+    EXPECT_TRUE(signals.touch);
 
     lr1.vote = LayerVoteType::ExplicitDefault;
     lr1.desiredRefreshRate = 60_Hz;
@@ -1077,14 +997,12 @@
     lr2.vote = LayerVoteType::Heuristic;
     lr2.desiredRefreshRate = 60_Hz;
     lr2.name = "60Hz Heuristic";
-    refreshRateConfigs->getBestRefreshRate(layers, {.touch = true}, &consideredSignals);
-    EXPECT_EQ(false, consideredSignals.touch);
+    std::tie(std::ignore, signals) = configs.getBestRefreshRate(layers, {.touch = true});
+    EXPECT_FALSE(signals.touch);
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitDefault) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90_72_120Device, /*currentConfigId=*/
-                                                 HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90_72_120, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
@@ -1116,7 +1034,7 @@
         ss << "ExplicitDefault " << desired;
         lr.name = ss.str();
 
-        const auto refreshRate = refreshRateConfigs->getBestRefreshRate(layers, {});
+        const auto refreshRate = configs.getBestRefreshRate(layers);
         EXPECT_EQ(refreshRate.getFps(), expected);
     }
 }
@@ -1128,39 +1046,36 @@
 
     // Test that 23.976 will choose 24 if 23.976 is not supported
     {
-        android::DisplayModes modes = {mConfig24,     mConfig25, mConfig30,
-                                       mConfig30Frac, mConfig60, mConfig60Frac};
-        auto refreshRateConfigs =
-                std::make_unique<RefreshRateConfigs>(modes, /*currentConfigId=*/HWC_CONFIG_ID_60);
+        TestableRefreshRateConfigs configs({kMode24, kMode25, kMode30, kMode30Frac, kMode60,
+                                            kMode60Frac},
+                                           kModeId60);
 
         lr.vote = LayerVoteType::ExplicitExactOrMultiple;
         lr.desiredRefreshRate = 23.976_Hz;
         lr.name = "ExplicitExactOrMultiple 23.976 Hz";
-        EXPECT_EQ(HWC_CONFIG_ID_24, refreshRateConfigs->getBestRefreshRate(layers, {}).getModeId());
+        EXPECT_EQ(kModeId24, configs.getBestRefreshRate(layers).getModeId());
     }
 
     // Test that 24 will choose 23.976 if 24 is not supported
     {
-        android::DisplayModes modes = {mConfig24Frac, mConfig25, mConfig30,
-                                       mConfig30Frac, mConfig60, mConfig60Frac};
-        auto refreshRateConfigs =
-                std::make_unique<RefreshRateConfigs>(modes, /*currentConfigId=*/HWC_CONFIG_ID_60);
+        TestableRefreshRateConfigs configs({kMode24Frac, kMode25, kMode30, kMode30Frac, kMode60,
+                                            kMode60Frac},
+                                           kModeId60);
+
         lr.desiredRefreshRate = 24_Hz;
         lr.name = "ExplicitExactOrMultiple 24 Hz";
-        EXPECT_EQ(HWC_CONFIG_ID_24_FRAC,
-                  refreshRateConfigs->getBestRefreshRate(layers, {}).getModeId());
+        EXPECT_EQ(kModeId24Frac, configs.getBestRefreshRate(layers).getModeId());
     }
 
     // Test that 29.97 will prefer 59.94 over 60 and 30
     {
-        android::DisplayModes modes = {mConfig24, mConfig24Frac, mConfig25,
-                                       mConfig30, mConfig60,     mConfig60Frac};
-        auto refreshRateConfigs =
-                std::make_unique<RefreshRateConfigs>(modes, /*currentConfigId=*/HWC_CONFIG_ID_60);
+        TestableRefreshRateConfigs configs({kMode24, kMode24Frac, kMode25, kMode30, kMode60,
+                                            kMode60Frac},
+                                           kModeId60);
+
         lr.desiredRefreshRate = 29.97_Hz;
         lr.name = "ExplicitExactOrMultiple 29.97 Hz";
-        EXPECT_EQ(HWC_CONFIG_ID_60_FRAC,
-                  refreshRateConfigs->getBestRefreshRate(layers, {}).getModeId());
+        EXPECT_EQ(kModeId60Frac, configs.getBestRefreshRate(layers).getModeId());
     }
 }
 
@@ -1170,9 +1085,7 @@
 
     // Test that voting for supported refresh rate will select this refresh rate
     {
-        auto refreshRateConfigs =
-                std::make_unique<RefreshRateConfigs>(m24_25_30_50_60WithFracDevice,
-                                                     /*currentConfigId=*/HWC_CONFIG_ID_60);
+        TestableRefreshRateConfigs configs(kModes_24_25_30_50_60_Frac, kModeId60);
 
         for (auto desired : {23.976_Hz, 24_Hz, 25_Hz, 29.97_Hz, 30_Hz, 50_Hz, 59.94_Hz, 60_Hz}) {
             lr.vote = LayerVoteType::ExplicitExact;
@@ -1181,69 +1094,61 @@
             ss << "ExplicitExact " << desired;
             lr.name = ss.str();
 
-            auto selectedRefreshRate = refreshRateConfigs->getBestRefreshRate(layers, {});
+            auto selectedRefreshRate = configs.getBestRefreshRate(layers);
             EXPECT_EQ(selectedRefreshRate.getFps(), lr.desiredRefreshRate);
         }
     }
 
     // Test that 23.976 will choose 24 if 23.976 is not supported
     {
-        android::DisplayModes modes = {mConfig24,     mConfig25, mConfig30,
-                                       mConfig30Frac, mConfig60, mConfig60Frac};
-        auto refreshRateConfigs =
-                std::make_unique<RefreshRateConfigs>(modes, /*currentConfigId=*/HWC_CONFIG_ID_60);
+        TestableRefreshRateConfigs configs({kMode24, kMode25, kMode30, kMode30Frac, kMode60,
+                                            kMode60Frac},
+                                           kModeId60);
+
         lr.vote = LayerVoteType::ExplicitExact;
         lr.desiredRefreshRate = 23.976_Hz;
         lr.name = "ExplicitExact 23.976 Hz";
-        EXPECT_EQ(HWC_CONFIG_ID_24, refreshRateConfigs->getBestRefreshRate(layers, {}).getModeId());
+        EXPECT_EQ(kModeId24, configs.getBestRefreshRate(layers).getModeId());
     }
 
     // Test that 24 will choose 23.976 if 24 is not supported
     {
-        android::DisplayModes modes = {mConfig24Frac, mConfig25, mConfig30,
-                                       mConfig30Frac, mConfig60, mConfig60Frac};
-        auto refreshRateConfigs =
-                std::make_unique<RefreshRateConfigs>(modes, /*currentConfigId=*/HWC_CONFIG_ID_60);
+        TestableRefreshRateConfigs configs({kMode24Frac, kMode25, kMode30, kMode30Frac, kMode60,
+                                            kMode60Frac},
+                                           kModeId60);
+
         lr.desiredRefreshRate = 24_Hz;
         lr.name = "ExplicitExact 24 Hz";
-        EXPECT_EQ(HWC_CONFIG_ID_24_FRAC,
-                  refreshRateConfigs->getBestRefreshRate(layers, {}).getModeId());
+        EXPECT_EQ(kModeId24Frac, configs.getBestRefreshRate(layers).getModeId());
     }
 }
 
 TEST_F(RefreshRateConfigsTest,
        getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresTouchFlag) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_90);
+    RefreshRateConfigs configs(kModes_60_90, kModeId90);
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}}),
-              0);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}}), 0);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
-    RefreshRateConfigs::GlobalSignals consideredSignals;
     lr.vote = LayerVoteType::ExplicitDefault;
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz ExplicitDefault";
     lr.focused = true;
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = true},
-                                                     &consideredSignals));
-    EXPECT_EQ(false, consideredSignals.touch);
+
+    const auto [refreshRate, signals] =
+            configs.getBestRefreshRate(layers, {.touch = true, .idle = true});
+
+    EXPECT_EQ(refreshRate, asRefreshRate(kMode60));
+    EXPECT_FALSE(signals.touch);
 }
 
 TEST_F(RefreshRateConfigsTest,
        getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresIdleFlag) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_60, {60_Hz, 60_Hz}, {60_Hz, 90_Hz}}),
-              0);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 90_Hz}}), 0);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
@@ -1252,23 +1157,18 @@
     lr.desiredRefreshRate = 90_Hz;
     lr.name = "90Hz ExplicitDefault";
     lr.focused = true;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {.idle = true}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers, {.idle = true}));
 }
 
 TEST_F(RefreshRateConfigsTest,
        getBestRefreshRate_withDisplayManagerRequestingSingleRate_onlySwitchesRatesForExplicitFocusedLayers) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_90);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId90);
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}}),
-              0);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}}), 0);
 
-    RefreshRateConfigs::GlobalSignals consideredSignals;
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate({}, {}, &consideredSignals));
-    EXPECT_EQ(false, consideredSignals.touch);
+    const auto [refreshRate, signals] = configs.getBestRefreshRateAndSignals({}, {});
+    EXPECT_EQ(refreshRate, asRefreshRate(kMode90));
+    EXPECT_FALSE(signals.touch);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
@@ -1277,52 +1177,50 @@
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz ExplicitExactOrMultiple";
     lr.focused = false;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.focused = true;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::ExplicitDefault;
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz ExplicitDefault";
     lr.focused = false;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.focused = true;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Heuristic;
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
     lr.focused = false;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.focused = true;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Max;
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Max";
     lr.focused = false;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.focused = true;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Min;
     lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Min";
     lr.focused = false;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     lr.focused = true;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, groupSwitchingNotAllowed) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
 
     // The default policy doesn't allow group switching. Verify that no
     // group switches are performed.
@@ -1334,17 +1232,16 @@
     layer.name = "90Hz ExplicitDefault";
     layer.focused = true;
 
-    ASSERT_EQ(HWC_CONFIG_ID_60, refreshRateConfigs->getBestRefreshRate(layers, {}).getModeId());
+    EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers).getModeId());
 }
 
 TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayer) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
+
     RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
     policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& layer = layers[0];
@@ -1353,17 +1250,16 @@
     layer.seamlessness = Seamlessness::SeamedAndSeamless;
     layer.name = "90Hz ExplicitDefault";
     layer.focused = true;
-    ASSERT_EQ(HWC_CONFIG_ID_90, refreshRateConfigs->getBestRefreshRate(layers, {}).getModeId());
+    EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers).getModeId());
 }
 
 TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerOnlySeamless) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
+
     RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
     policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
 
     // Verify that we won't change the group if seamless switch is required.
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
@@ -1373,19 +1269,18 @@
     layer.seamlessness = Seamlessness::OnlySeamless;
     layer.name = "90Hz ExplicitDefault";
     layer.focused = true;
-    ASSERT_EQ(HWC_CONFIG_ID_60, refreshRateConfigs->getBestRefreshRate(layers, {}).getModeId());
+    EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers).getModeId());
 }
 
 TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerOnlySeamlessDefaultFps) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
-    RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
+    RefreshRateConfigs::Policy policy;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
+
+    configs.setCurrentModeId(kModeId90);
 
     // Verify that we won't do a seamless switch if we request the same mode as the default
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
@@ -1395,19 +1290,18 @@
     layer.seamlessness = Seamlessness::OnlySeamless;
     layer.name = "60Hz ExplicitDefault";
     layer.focused = true;
-    ASSERT_EQ(HWC_CONFIG_ID_90, refreshRateConfigs->getBestRefreshRate(layers, {}).getModeId());
+    EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers).getModeId());
 }
 
 TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerDefaultSeamlessness) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
-    RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
+    RefreshRateConfigs::Policy policy;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
+
+    configs.setCurrentModeId(kModeId90);
 
     // Verify that if the current config is in another group and there are no layers with
     // seamlessness=SeamedAndSeamless we'll go back to the default group.
@@ -1420,19 +1314,18 @@
     layer.name = "60Hz ExplicitDefault";
     layer.focused = true;
 
-    ASSERT_EQ(HWC_CONFIG_ID_60, refreshRateConfigs->getBestRefreshRate(layers, {}).getModeId());
+    EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers).getModeId());
 }
 
 TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersOnlySeamlessAndSeamed) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
-    RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
+    RefreshRateConfigs::Policy policy;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
+
+    configs.setCurrentModeId(kModeId90);
 
     // If there's a layer with seamlessness=SeamedAndSeamless, another layer with
     // seamlessness=OnlySeamless can't change the mode group.
@@ -1450,19 +1343,18 @@
     layers[1].name = "90Hz ExplicitDefault";
     layers[1].focused = false;
 
-    ASSERT_EQ(HWC_CONFIG_ID_90, refreshRateConfigs->getBestRefreshRate(layers, {}).getModeId());
+    EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers).getModeId());
 }
 
 TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersDefaultFocusedAndSeamed) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
-    RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
+    RefreshRateConfigs::Policy policy;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
+
+    configs.setCurrentModeId(kModeId90);
 
     // If there's a focused layer with seamlessness=SeamedAndSeamless, another layer with
     // seamlessness=Default can't change the mode group back to the group of the default
@@ -1484,19 +1376,18 @@
     layers[1].vote = LayerVoteType::ExplicitDefault;
     layers[1].name = "90Hz ExplicitDefault";
 
-    ASSERT_EQ(HWC_CONFIG_ID_90, refreshRateConfigs->getBestRefreshRate(layers, {}).getModeId());
+    EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers).getModeId());
 }
 
 TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersDefaultNotFocusedAndSeamed) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
-    RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
+    RefreshRateConfigs::Policy policy;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
+
+    configs.setCurrentModeId(kModeId90);
 
     // Layer with seamlessness=Default can change the mode group if there's a not
     // focused layer with seamlessness=SeamedAndSeamless. This happens for example,
@@ -1515,19 +1406,17 @@
     layers[1].vote = LayerVoteType::ExplicitDefault;
     layers[1].name = "90Hz ExplicitDefault";
 
-    ASSERT_EQ(HWC_CONFIG_ID_60, refreshRateConfigs->getBestRefreshRate(layers, {}).getModeId());
+    EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers).getModeId());
 }
 
 TEST_F(RefreshRateConfigsTest, nonSeamlessVotePrefersSeamlessSwitches) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_30_60, kModeId60);
 
     // Allow group switching.
     RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
     policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& layer = layers[0];
@@ -1537,22 +1426,20 @@
     layer.name = "60Hz ExplicitExactOrMultiple";
     layer.focused = true;
 
-    ASSERT_EQ(HWC_CONFIG_ID_60, refreshRateConfigs->getBestRefreshRate(layers, {}).getModeId());
+    EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers).getModeId());
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_120);
-    ASSERT_EQ(HWC_CONFIG_ID_120, refreshRateConfigs->getBestRefreshRate(layers, {}).getModeId());
+    configs.setCurrentModeId(kModeId120);
+    EXPECT_EQ(kModeId120, configs.getBestRefreshRate(layers).getModeId());
 }
 
 TEST_F(RefreshRateConfigsTest, nonSeamlessExactAndSeamlessMultipleLayers) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m25_30_50_60Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_25_30_50_60, kModeId60);
 
     // Allow group switching.
     RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
     policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
 
     std::vector<LayerRequirement> layers = {{.name = "60Hz ExplicitDefault",
                                              .vote = LayerVoteType::ExplicitDefault,
@@ -1567,37 +1454,33 @@
                                              .weight = 1.f,
                                              .focused = true}};
 
-    ASSERT_EQ(HWC_CONFIG_ID_50, refreshRateConfigs->getBestRefreshRate(layers, {}).getModeId());
+    EXPECT_EQ(kModeId50, configs.getBestRefreshRate(layers).getModeId());
 
     auto& seamedLayer = layers[0];
     seamedLayer.desiredRefreshRate = 30_Hz;
     seamedLayer.name = "30Hz ExplicitDefault";
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_30);
+    configs.setCurrentModeId(kModeId30);
 
-    ASSERT_EQ(HWC_CONFIG_ID_25, refreshRateConfigs->getBestRefreshRate(layers, {}).getModeId());
+    EXPECT_EQ(kModeId25, configs.getBestRefreshRate(layers).getModeId());
 }
 
 TEST_F(RefreshRateConfigsTest, minLayersDontTrigerSeamedSwitch) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_90);
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId90);
 
     // Allow group switching.
     RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
     policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
 
     std::vector<LayerRequirement> layers = {
             {.name = "Min", .vote = LayerVoteType::Min, .weight = 1.f, .focused = true}};
 
-    ASSERT_EQ(HWC_CONFIG_ID_90, refreshRateConfigs->getBestRefreshRate(layers, {}).getModeId());
+    EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers).getModeId());
 }
 
 TEST_F(RefreshRateConfigsTest, primaryVsAppRequestPolicy) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     layers[0].name = "Test layer";
@@ -1613,50 +1496,45 @@
         layers[0].vote = voteType;
         layers[0].desiredRefreshRate = fps;
         layers[0].focused = args.focused;
-        return refreshRateConfigs->getBestRefreshRate(layers, {.touch = args.touch}).getModeId();
+        return configs.getBestRefreshRate(layers, {.touch = args.touch}).getModeId();
     };
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_60, {30_Hz, 60_Hz}, {30_Hz, 90_Hz}}),
-              0);
-    EXPECT_EQ(HWC_CONFIG_ID_60, refreshRateConfigs->getBestRefreshRate({}, {}).getModeId());
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::NoVote, 90_Hz));
-    EXPECT_EQ(HWC_CONFIG_ID_30, getFrameRate(LayerVoteType::Min, 90_Hz));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, 90_Hz));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Heuristic, 90_Hz));
-    EXPECT_EQ(HWC_CONFIG_ID_90, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz));
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 60_Hz}, {30_Hz, 90_Hz}}), 0);
+
+    EXPECT_EQ(kModeId60, configs.getBestRefreshRate().getModeId());
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz));
+    EXPECT_EQ(kModeId30, getFrameRate(LayerVoteType::Min, 90_Hz));
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz));
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Heuristic, 90_Hz));
+    EXPECT_EQ(kModeId90, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz));
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz));
 
     // Unfocused layers are not allowed to override primary config.
-    EXPECT_EQ(HWC_CONFIG_ID_60,
-              getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz, {.focused = false}));
-    EXPECT_EQ(HWC_CONFIG_ID_60,
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz, {.focused = false}));
+    EXPECT_EQ(kModeId60,
               getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz, {.focused = false}));
 
     // Touch boost should be restricted to the primary range.
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, 90_Hz, {.touch = true}));
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz, {.touch = true}));
+
     // When we're higher than the primary range max due to a layer frame rate setting, touch boost
     // shouldn't drag us back down to the primary range max.
-    EXPECT_EQ(HWC_CONFIG_ID_90,
-              getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz, {.touch = true}));
-    EXPECT_EQ(HWC_CONFIG_ID_60,
+    EXPECT_EQ(kModeId90, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz, {.touch = true}));
+    EXPECT_EQ(kModeId60,
               getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz, {.touch = true}));
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_60, {60_Hz, 60_Hz}, {60_Hz, 60_Hz}}),
-              0);
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::NoVote, 90_Hz));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Min, 90_Hz));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, 90_Hz));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Heuristic, 90_Hz));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz));
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 60_Hz}}), 0);
+
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz));
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Min, 90_Hz));
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz));
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Heuristic, 90_Hz));
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz));
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz));
 }
 
 TEST_F(RefreshRateConfigsTest, idle) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     layers[0].name = "Test layer";
@@ -1664,82 +1542,70 @@
     const auto getIdleFrameRate = [&](LayerVoteType voteType, bool touchActive) -> DisplayModeId {
         layers[0].vote = voteType;
         layers[0].desiredRefreshRate = 90_Hz;
-        RefreshRateConfigs::GlobalSignals consideredSignals;
-        const auto configId =
-                refreshRateConfigs
-                        ->getBestRefreshRate(layers, {.touch = touchActive, .idle = true},
-                                             &consideredSignals)
-                        .getModeId();
-        // Refresh rate will be chosen by either touch state or idle state
-        EXPECT_EQ(!touchActive, consideredSignals.idle);
-        return configId;
+
+        const auto [refreshRate, signals] =
+                configs.getBestRefreshRateAndSignals(layers, {.touch = touchActive, .idle = true});
+
+        // Refresh rate will be chosen by either touch state or idle state.
+        EXPECT_EQ(!touchActive, signals.idle);
+        return refreshRate.getModeId();
     };
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_60, {60_Hz, 90_Hz}, {60_Hz, 90_Hz}}),
-              0);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}, {60_Hz, 90_Hz}}), 0);
 
     // Idle should be lower priority than touch boost.
-    EXPECT_EQ(HWC_CONFIG_ID_90, getIdleFrameRate(LayerVoteType::NoVote, /*touchActive=*/true));
-    EXPECT_EQ(HWC_CONFIG_ID_90, getIdleFrameRate(LayerVoteType::Min, /*touchActive=*/true));
-    EXPECT_EQ(HWC_CONFIG_ID_90, getIdleFrameRate(LayerVoteType::Max, /*touchActive=*/true));
-    EXPECT_EQ(HWC_CONFIG_ID_90, getIdleFrameRate(LayerVoteType::Heuristic, /*touchActive=*/true));
-    EXPECT_EQ(HWC_CONFIG_ID_90,
-              getIdleFrameRate(LayerVoteType::ExplicitDefault, /*touchActive=*/true));
-    EXPECT_EQ(HWC_CONFIG_ID_90,
-              getIdleFrameRate(LayerVoteType::ExplicitExactOrMultiple, /*touchActive=*/true));
+    {
+        constexpr bool kTouchActive = true;
+        EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::NoVote, kTouchActive));
+        EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::Min, kTouchActive));
+        EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::Max, kTouchActive));
+        EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::Heuristic, kTouchActive));
+        EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::ExplicitDefault, kTouchActive));
+        EXPECT_EQ(kModeId90,
+                  getIdleFrameRate(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
+    }
 
     // With no layers, idle should still be lower priority than touch boost.
-    EXPECT_EQ(HWC_CONFIG_ID_90,
-              refreshRateConfigs->getBestRefreshRate({}, {.touch = true, .idle = true})
-                      .getModeId());
+    EXPECT_EQ(kModeId90, configs.getBestRefreshRate({}, {.touch = true, .idle = true}).getModeId());
 
     // Idle should be higher precedence than other layer frame rate considerations.
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
-    EXPECT_EQ(HWC_CONFIG_ID_60, getIdleFrameRate(LayerVoteType::NoVote, /*touchActive=*/false));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getIdleFrameRate(LayerVoteType::Min, /*touchActive=*/false));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getIdleFrameRate(LayerVoteType::Max, /*touchActive=*/false));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getIdleFrameRate(LayerVoteType::Heuristic, /*touchActive=*/false));
-    EXPECT_EQ(HWC_CONFIG_ID_60,
-              getIdleFrameRate(LayerVoteType::ExplicitDefault, /*touchActive=*/false));
-    EXPECT_EQ(HWC_CONFIG_ID_60,
-              getIdleFrameRate(LayerVoteType::ExplicitExactOrMultiple, /*touchActive=*/false));
+    configs.setCurrentModeId(kModeId90);
+
+    {
+        constexpr bool kTouchActive = false;
+        EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::NoVote, kTouchActive));
+        EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::Min, kTouchActive));
+        EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::Max, kTouchActive));
+        EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::Heuristic, kTouchActive));
+        EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::ExplicitDefault, kTouchActive));
+        EXPECT_EQ(kModeId60,
+                  getIdleFrameRate(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
+    }
 
     // Idle should be applied rather than the current config when there are no layers.
-    EXPECT_EQ(HWC_CONFIG_ID_60,
-              refreshRateConfigs->getBestRefreshRate({}, {.idle = true}).getModeId());
+    EXPECT_EQ(kModeId60, configs.getBestRefreshRate({}, {.idle = true}).getModeId());
 }
 
 TEST_F(RefreshRateConfigsTest, findClosestKnownFrameRate) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
     for (float fps = 1.0f; fps <= 120.0f; fps += 0.1f) {
-        const auto knownFrameRate =
-                findClosestKnownFrameRate(*refreshRateConfigs, Fps::fromValue(fps));
-        Fps expectedFrameRate;
-        if (fps < 26.91f) {
-            expectedFrameRate = 24_Hz;
-        } else if (fps < 37.51f) {
-            expectedFrameRate = 30_Hz;
-        } else if (fps < 52.51f) {
-            expectedFrameRate = 45_Hz;
-        } else if (fps < 66.01f) {
-            expectedFrameRate = 60_Hz;
-        } else if (fps < 81.01f) {
-            expectedFrameRate = 72_Hz;
-        } else {
-            expectedFrameRate = 90_Hz;
-        }
+        const auto knownFrameRate = configs.findClosestKnownFrameRate(Fps::fromValue(fps));
+        const Fps expectedFrameRate = [fps] {
+            if (fps < 26.91f) return 24_Hz;
+            if (fps < 37.51f) return 30_Hz;
+            if (fps < 52.51f) return 45_Hz;
+            if (fps < 66.01f) return 60_Hz;
+            if (fps < 81.01f) return 72_Hz;
+            return 90_Hz;
+        }();
+
         EXPECT_EQ(expectedFrameRate, knownFrameRate);
     }
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_KnownFrameRate) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
     struct Expectation {
         Fps fps;
@@ -1747,13 +1613,14 @@
     };
 
     const std::initializer_list<Expectation> knownFrameRatesExpectations = {
-            {24_Hz, mExpected60Config}, {30_Hz, mExpected60Config}, {45_Hz, mExpected90Config},
-            {60_Hz, mExpected60Config}, {72_Hz, mExpected90Config}, {90_Hz, mExpected90Config},
+            {24_Hz, asRefreshRate(kMode60)}, {30_Hz, asRefreshRate(kMode60)},
+            {45_Hz, asRefreshRate(kMode90)}, {60_Hz, asRefreshRate(kMode60)},
+            {72_Hz, asRefreshRate(kMode90)}, {90_Hz, asRefreshRate(kMode90)},
     };
 
     // Make sure the test tests all the known frame rate
-    const auto knownFrameRateList = getKnownFrameRate(*refreshRateConfigs);
-    const bool equal = std::equal(knownFrameRateList.begin(), knownFrameRateList.end(),
+    const auto& knownFrameRates = configs.knownFrameRates();
+    const bool equal = std::equal(knownFrameRates.begin(), knownFrameRates.end(),
                                   knownFrameRatesExpectations.begin(),
                                   [](Fps fps, const Expectation& expected) {
                                       return isApproxEqual(fps, expected.fps);
@@ -1766,14 +1633,12 @@
 
     for (const auto& [fps, refreshRate] : knownFrameRatesExpectations) {
         layer.desiredRefreshRate = fps;
-        EXPECT_EQ(refreshRate, refreshRateConfigs->getBestRefreshRate(layers, {}));
+        EXPECT_EQ(refreshRate, configs.getBestRefreshRate(layers));
     }
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExact) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
     auto& explicitExactLayer = layers[0];
@@ -1787,28 +1652,26 @@
     explicitExactLayer.name = "ExplicitExact";
     explicitExactLayer.desiredRefreshRate = 30_Hz;
 
-    EXPECT_EQ(mExpected30Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
-    EXPECT_EQ(mExpected30Config, refreshRateConfigs->getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(asRefreshRate(kMode30), configs.getBestRefreshRate(layers));
+    EXPECT_EQ(asRefreshRate(kMode30), configs.getBestRefreshRate(layers, {.touch = true}));
 
     explicitExactOrMultipleLayer.desiredRefreshRate = 120_Hz;
     explicitExactLayer.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 
     explicitExactLayer.desiredRefreshRate = 72_Hz;
-    EXPECT_EQ(mExpected72Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode72), configs.getBestRefreshRate(layers));
 
     explicitExactLayer.desiredRefreshRate = 90_Hz;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     explicitExactLayer.desiredRefreshRate = 120_Hz;
-    EXPECT_EQ(mExpected120Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode120), configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExactEnableFrameRateOverride) {
-    RefreshRateConfigs::Config config = {.enableFrameRateOverride = true};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60, config);
+    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60,
+                                       {.enableFrameRateOverride = true});
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
     auto& explicitExactLayer = layers[0];
@@ -1822,92 +1685,55 @@
     explicitExactLayer.name = "ExplicitExact";
     explicitExactLayer.desiredRefreshRate = 30_Hz;
 
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
-    EXPECT_EQ(mExpected120Config, refreshRateConfigs->getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
+    EXPECT_EQ(asRefreshRate(kMode120), configs.getBestRefreshRate(layers, {.touch = true}));
 
     explicitExactOrMultipleLayer.desiredRefreshRate = 120_Hz;
     explicitExactLayer.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(mExpected120Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode120), configs.getBestRefreshRate(layers));
 
     explicitExactLayer.desiredRefreshRate = 72_Hz;
-    EXPECT_EQ(mExpected72Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode72), configs.getBestRefreshRate(layers));
 
     explicitExactLayer.desiredRefreshRate = 90_Hz;
-    EXPECT_EQ(mExpected90Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode90), configs.getBestRefreshRate(layers));
 
     explicitExactLayer.desiredRefreshRate = 120_Hz;
-    EXPECT_EQ(mExpected120Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode120), configs.getBestRefreshRate(layers));
 }
 
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ReadsCached) {
+TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ReadsCache) {
+    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
+
     using GlobalSignals = RefreshRateConfigs::GlobalSignals;
+    const auto args = std::make_pair(std::vector<LayerRequirement>{},
+                                     GlobalSignals{.touch = true, .idle = true});
+    const auto result = std::make_pair(asRefreshRate(kMode90), GlobalSignals{.touch = true});
 
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    configs.mutableGetBestRefreshRateCache() = {args, result};
 
-    setLastBestRefreshRateInvocation(*refreshRateConfigs,
-                                     GetBestRefreshRateInvocation{.globalSignals = {.touch = true,
-                                                                                    .idle = true},
-                                                                  .outSignalsConsidered =
-                                                                          {.touch = true},
-                                                                  .resultingBestRefreshRate =
-                                                                          createRefreshRate(
-                                                                                  mConfig90)});
-
-    EXPECT_EQ(createRefreshRate(mConfig90),
-              refreshRateConfigs->getBestRefreshRate({}, {.touch = true, .idle = true}));
-
-    const GlobalSignals cachedSignalsConsidered{.touch = true};
-    setLastBestRefreshRateInvocation(*refreshRateConfigs,
-                                     GetBestRefreshRateInvocation{.globalSignals = {.touch = true,
-                                                                                    .idle = true},
-                                                                  .outSignalsConsidered =
-                                                                          cachedSignalsConsidered,
-                                                                  .resultingBestRefreshRate =
-                                                                          createRefreshRate(
-                                                                                  mConfig30)});
-
-    GlobalSignals signalsConsidered;
-    EXPECT_EQ(createRefreshRate(mConfig30),
-              refreshRateConfigs->getBestRefreshRate({}, {.touch = true, .idle = true},
-                                                     &signalsConsidered));
-
-    EXPECT_EQ(cachedSignalsConsidered, signalsConsidered);
+    EXPECT_EQ(result, configs.getBestRefreshRateAndSignals(args.first, args.second));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_WritesCache) {
-    using GlobalSignals = RefreshRateConfigs::GlobalSignals;
+    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
 
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
-    ASSERT_FALSE(getLastBestRefreshRateInvocation(*refreshRateConfigs).has_value());
+    EXPECT_FALSE(configs.mutableGetBestRefreshRateCache());
 
-    GlobalSignals globalSignals{.touch = true, .idle = true};
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
-    const auto lastResult =
-            refreshRateConfigs->getBestRefreshRate(layers, globalSignals,
-                                                   /* outSignalsConsidered */ nullptr);
+    RefreshRateConfigs::GlobalSignals globalSignals{.touch = true, .idle = true};
 
-    const auto lastInvocation = getLastBestRefreshRateInvocation(*refreshRateConfigs);
+    const auto result = configs.getBestRefreshRateAndSignals(layers, globalSignals);
 
-    ASSERT_TRUE(lastInvocation.has_value());
-    ASSERT_EQ(layers, lastInvocation->layerRequirements);
-    ASSERT_EQ(globalSignals, lastInvocation->globalSignals);
-    ASSERT_EQ(lastResult, lastInvocation->resultingBestRefreshRate);
+    const auto& cache = configs.mutableGetBestRefreshRateCache();
+    ASSERT_TRUE(cache);
 
-    // outSignalsConsidered needs to be populated even tho earlier we gave nullptr
-    // to getBestRefreshRate()
-    GlobalSignals detaultSignals;
-    ASSERT_FALSE(detaultSignals == lastInvocation->outSignalsConsidered);
+    EXPECT_EQ(cache->arguments, std::make_pair(layers, globalSignals));
+    EXPECT_EQ(cache->result, result);
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExactTouchBoost) {
-    RefreshRateConfigs::Config config = {.enableFrameRateOverride = true};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60, config);
+    TestableRefreshRateConfigs configs(kModes_60_120, kModeId60, {.enableFrameRateOverride = true});
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
     auto& explicitExactLayer = layers[0];
@@ -1921,20 +1747,18 @@
     explicitExactLayer.name = "ExplicitExact";
     explicitExactLayer.desiredRefreshRate = 30_Hz;
 
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
-    EXPECT_EQ(mExpected120Config, refreshRateConfigs->getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
+    EXPECT_EQ(asRefreshRate(kMode120), configs.getBestRefreshRate(layers, {.touch = true}));
 
     explicitExactOrMultipleLayer.vote = LayerVoteType::NoVote;
 
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {.touch = true}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers, {.touch = true}));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_FractionalRefreshRates_ExactAndDefault) {
-    RefreshRateConfigs::Config config = {.enableFrameRateOverride = true};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m24_25_30_50_60WithFracDevice,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60, config);
+    TestableRefreshRateConfigs configs(kModes_24_25_30_50_60_Frac, kModeId60,
+                                       {.enableFrameRateOverride = true});
 
     std::vector<LayerRequirement> layers = {{.weight = 0.5f}, {.weight = 0.5f}};
     auto& explicitDefaultLayer = layers[0];
@@ -1948,32 +1772,27 @@
     explicitDefaultLayer.name = "ExplicitDefault";
     explicitDefaultLayer.desiredRefreshRate = 59.94_Hz;
 
-    EXPECT_EQ(mExpected60Config, refreshRateConfigs->getBestRefreshRate(layers, {}));
+    EXPECT_EQ(asRefreshRate(kMode60), configs.getBestRefreshRate(layers));
 }
 
 // b/190578904
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_deviceWithCloseRefreshRates) {
+TEST_F(RefreshRateConfigsTest, getBestRefreshRate_withCloseRefreshRates) {
     constexpr int kMinRefreshRate = 10;
     constexpr int kMaxRefreshRate = 240;
 
     DisplayModes displayModes;
     for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
-        constexpr int32_t kGroup = 0;
-        const auto refreshRate = Fps::fromValue(static_cast<float>(fps));
         displayModes.push_back(
-                createDisplayMode(DisplayModeId(fps), kGroup, refreshRate.getPeriodNsecs()));
+                createDisplayMode(DisplayModeId(fps), Fps::fromValue(static_cast<float>(fps))));
     }
 
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(displayModes,
-                                                 /*currentConfigId=*/displayModes[0]->getId());
+    const TestableRefreshRateConfigs configs(displayModes, displayModes[0]->getId());
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) {
         layers[0].desiredRefreshRate = fps;
         layers[0].vote = vote;
-        EXPECT_EQ(fps.getIntValue(),
-                  refreshRateConfigs->getBestRefreshRate(layers, {}).getFps().getIntValue())
+        EXPECT_EQ(fps.getIntValue(), configs.getBestRefreshRate(layers).getFps().getIntValue())
                 << "Failed for " << ftl::enum_string(vote);
     };
 
@@ -1986,86 +1805,110 @@
     }
 }
 
+// b/190578904
+TEST_F(RefreshRateConfigsTest, getBestRefreshRate_conflictingVotes) {
+    const DisplayModes displayModes = {
+            createDisplayMode(DisplayModeId(0), 43_Hz),
+            createDisplayMode(DisplayModeId(1), 53_Hz),
+            createDisplayMode(DisplayModeId(2), 55_Hz),
+            createDisplayMode(DisplayModeId(3), 60_Hz),
+    };
+
+    const RefreshRateConfigs::GlobalSignals globalSignals = {.touch = false, .idle = false};
+    const TestableRefreshRateConfigs configs(displayModes, displayModes[0]->getId());
+
+    const std::vector<LayerRequirement> layers = {
+            {
+                    .vote = LayerVoteType::ExplicitDefault,
+                    .desiredRefreshRate = 43_Hz,
+                    .seamlessness = Seamlessness::SeamedAndSeamless,
+                    .weight = 0.41f,
+            },
+            {
+                    .vote = LayerVoteType::ExplicitExactOrMultiple,
+                    .desiredRefreshRate = 53_Hz,
+                    .seamlessness = Seamlessness::SeamedAndSeamless,
+                    .weight = 0.41f,
+            },
+    };
+
+    EXPECT_EQ(53_Hz, configs.getBestRefreshRate(layers, globalSignals).getFps());
+}
+
 TEST_F(RefreshRateConfigsTest, testComparisonOperator) {
-    EXPECT_TRUE(mExpected60Config < mExpected90Config);
-    EXPECT_FALSE(mExpected60Config < mExpected60Config);
-    EXPECT_FALSE(mExpected90Config < mExpected90Config);
+    EXPECT_TRUE(asRefreshRate(kMode60) < asRefreshRate(kMode90));
+    EXPECT_FALSE(asRefreshRate(kMode60) < asRefreshRate(kMode60));
+    EXPECT_FALSE(asRefreshRate(kMode90) < asRefreshRate(kMode90));
 }
 
 TEST_F(RefreshRateConfigsTest, testKernelIdleTimerAction) {
-    using KernelIdleTimerAction = scheduler::RefreshRateConfigs::KernelIdleTimerAction;
+    using KernelIdleTimerAction = RefreshRateConfigs::KernelIdleTimerAction;
 
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_90);
+    RefreshRateConfigs configs(kModes_60_90, kModeId90);
+
     // SetPolicy(60, 90), current 90Hz => TurnOn.
-    EXPECT_EQ(KernelIdleTimerAction::TurnOn, refreshRateConfigs->getIdleTimerAction());
+    EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction());
 
     // SetPolicy(60, 90), current 60Hz => TurnOn.
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60_Hz, 90_Hz}}), 0);
-    EXPECT_EQ(KernelIdleTimerAction::TurnOn, refreshRateConfigs->getIdleTimerAction());
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}}), 0);
+    EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction());
 
     // SetPolicy(60, 60), current 60Hz => TurnOff
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60_Hz, 60_Hz}}), 0);
-    EXPECT_EQ(KernelIdleTimerAction::TurnOff, refreshRateConfigs->getIdleTimerAction());
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), 0);
+    EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction());
 
     // SetPolicy(90, 90), current 90Hz => TurnOff.
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {90_Hz, 90_Hz}}), 0);
-    EXPECT_EQ(KernelIdleTimerAction::TurnOff, refreshRateConfigs->getIdleTimerAction());
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}), 0);
+    EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction());
 }
 
 TEST_F(RefreshRateConfigsTest, testKernelIdleTimerActionFor120Hz) {
-    using KernelIdleTimerAction = scheduler::RefreshRateConfigs::KernelIdleTimerAction;
+    using KernelIdleTimerAction = RefreshRateConfigs::KernelIdleTimerAction;
 
-    // Tests with 120Hz
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_120);
+    RefreshRateConfigs configs(kModes_60_120, kModeId120);
+
     // SetPolicy(0, 60), current 60Hz => TurnOn.
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {0_Hz, 60_Hz}}), 0);
-    EXPECT_EQ(KernelIdleTimerAction::TurnOn, refreshRateConfigs->getIdleTimerAction());
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {0_Hz, 60_Hz}}), 0);
+    EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction());
 
     // SetPolicy(60, 60), current 60Hz => TurnOff.
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60_Hz, 60_Hz}}), 0);
-    EXPECT_EQ(KernelIdleTimerAction::TurnOff, refreshRateConfigs->getIdleTimerAction());
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), 0);
+    EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction());
 
     // SetPolicy(60, 120), current 60Hz => TurnOn.
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60_Hz, 120_Hz}}), 0);
-    EXPECT_EQ(KernelIdleTimerAction::TurnOn, refreshRateConfigs->getIdleTimerAction());
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 120_Hz}}), 0);
+    EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction());
 
     // SetPolicy(120, 120), current 120Hz => TurnOff.
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_120, {120_Hz, 120_Hz}}),
-              0);
-    EXPECT_EQ(KernelIdleTimerAction::TurnOff, refreshRateConfigs->getIdleTimerAction());
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId120, {120_Hz, 120_Hz}}), 0);
+    EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction());
 }
 
 TEST_F(RefreshRateConfigsTest, getFrameRateDivider) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_30);
+    RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId30);
 
     const auto frameRate = 30_Hz;
-    Fps displayRefreshRate = refreshRateConfigs->getCurrentRefreshRate().getFps();
+    Fps displayRefreshRate = configs.getCurrentRefreshRate().getFps();
     EXPECT_EQ(1, RefreshRateConfigs::getFrameRateDivider(displayRefreshRate, frameRate));
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_60);
-    displayRefreshRate = refreshRateConfigs->getCurrentRefreshRate().getFps();
+    configs.setCurrentModeId(kModeId60);
+    displayRefreshRate = configs.getCurrentRefreshRate().getFps();
     EXPECT_EQ(2, RefreshRateConfigs::getFrameRateDivider(displayRefreshRate, frameRate));
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_72);
-    displayRefreshRate = refreshRateConfigs->getCurrentRefreshRate().getFps();
+    configs.setCurrentModeId(kModeId72);
+    displayRefreshRate = configs.getCurrentRefreshRate().getFps();
     EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivider(displayRefreshRate, frameRate));
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
-    displayRefreshRate = refreshRateConfigs->getCurrentRefreshRate().getFps();
+    configs.setCurrentModeId(kModeId90);
+    displayRefreshRate = configs.getCurrentRefreshRate().getFps();
     EXPECT_EQ(3, RefreshRateConfigs::getFrameRateDivider(displayRefreshRate, frameRate));
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_120);
-    displayRefreshRate = refreshRateConfigs->getCurrentRefreshRate().getFps();
+    configs.setCurrentModeId(kModeId120);
+    displayRefreshRate = configs.getCurrentRefreshRate().getFps();
     EXPECT_EQ(4, RefreshRateConfigs::getFrameRateDivider(displayRefreshRate, frameRate));
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
-    displayRefreshRate = refreshRateConfigs->getCurrentRefreshRate().getFps();
+    configs.setCurrentModeId(kModeId90);
+    displayRefreshRate = configs.getCurrentRefreshRate().getFps();
     EXPECT_EQ(4, RefreshRateConfigs::getFrameRateDivider(displayRefreshRate, 22.5_Hz));
 
     EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivider(24_Hz, 25_Hz));
@@ -2101,57 +1944,52 @@
 }
 
 TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_noLayers) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device, /*currentConfigId=*/
-                                                 HWC_CONFIG_ID_120);
+    RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120);
 
-    ASSERT_TRUE(refreshRateConfigs->getFrameRateOverrides({}, 120_Hz, {}).empty());
+    EXPECT_TRUE(configs.getFrameRateOverrides({}, 120_Hz, {}).empty());
 }
 
 TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_60on120) {
-    RefreshRateConfigs::Config config = {.enableFrameRateOverride = true};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device, /*currentConfigId=*/
-                                                 HWC_CONFIG_ID_120, config);
+    RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120,
+                               {.enableFrameRateOverride = true});
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     layers[0].name = "Test layer";
     layers[0].ownerUid = 1234;
     layers[0].desiredRefreshRate = 60_Hz;
     layers[0].vote = LayerVoteType::ExplicitDefault;
-    auto frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, 120_Hz, {});
-    ASSERT_EQ(1, frameRateOverrides.size());
-    ASSERT_EQ(1, frameRateOverrides.count(1234));
-    ASSERT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+    auto frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
 
     layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
-    frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, 120_Hz, {});
-    ASSERT_EQ(1, frameRateOverrides.size());
-    ASSERT_EQ(1, frameRateOverrides.count(1234));
-    ASSERT_EQ(60_Hz, frameRateOverrides.at(1234));
+    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
 
     layers[0].vote = LayerVoteType::NoVote;
-    frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, 120_Hz, {});
-    ASSERT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
 
     layers[0].vote = LayerVoteType::Min;
-    frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, 120_Hz, {});
-    ASSERT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
 
     layers[0].vote = LayerVoteType::Max;
-    frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, 120_Hz, {});
-    ASSERT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
 
     layers[0].vote = LayerVoteType::Heuristic;
-    frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, 120_Hz, {});
-    ASSERT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
 }
 
 TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_twoUids) {
-    RefreshRateConfigs::Config config = {.enableFrameRateOverride = true};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device, /*currentConfigId=*/
-                                                 HWC_CONFIG_ID_120, config);
+    RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120,
+                               {.enableFrameRateOverride = true});
 
     std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f},
                                             {.ownerUid = 5678, .weight = 1.f}};
@@ -2163,69 +2001,64 @@
     layers[1].name = "Test layer 5678";
     layers[1].desiredRefreshRate = 30_Hz;
     layers[1].vote = LayerVoteType::ExplicitDefault;
-    auto frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, 120_Hz, {});
+    auto frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
 
-    ASSERT_EQ(2, frameRateOverrides.size());
-    ASSERT_EQ(1, frameRateOverrides.count(1234));
-    ASSERT_EQ(60_Hz, frameRateOverrides.at(1234));
-    ASSERT_EQ(1, frameRateOverrides.count(5678));
-    ASSERT_EQ(30_Hz, frameRateOverrides.at(5678));
+    EXPECT_EQ(2u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+    ASSERT_EQ(1u, frameRateOverrides.count(5678));
+    EXPECT_EQ(30_Hz, frameRateOverrides.at(5678));
 
     layers[1].vote = LayerVoteType::Heuristic;
-    frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, 120_Hz, {});
-    ASSERT_EQ(1, frameRateOverrides.size());
-    ASSERT_EQ(1, frameRateOverrides.count(1234));
-    ASSERT_EQ(60_Hz, frameRateOverrides.at(1234));
+    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
 
     layers[1].ownerUid = 1234;
-    frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, 120_Hz, {});
-    ASSERT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
 }
 
 TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_touch) {
-    RefreshRateConfigs::Config config = {.enableFrameRateOverride = true};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device, /*currentConfigId=*/
-                                                 HWC_CONFIG_ID_120, config);
+    RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120,
+                               {.enableFrameRateOverride = true});
 
     std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f}};
     layers[0].name = "Test layer";
     layers[0].desiredRefreshRate = 60_Hz;
     layers[0].vote = LayerVoteType::ExplicitDefault;
 
-    auto frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, 120_Hz, {});
-    ASSERT_EQ(1, frameRateOverrides.size());
-    ASSERT_EQ(1, frameRateOverrides.count(1234));
-    ASSERT_EQ(60_Hz, frameRateOverrides.at(1234));
+    auto frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
 
-    frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, 120_Hz, {.touch = true});
-    ASSERT_EQ(1, frameRateOverrides.size());
-    ASSERT_EQ(1, frameRateOverrides.count(1234));
-    ASSERT_EQ(60_Hz, frameRateOverrides.at(1234));
+    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
 
     layers[0].vote = LayerVoteType::ExplicitExact;
-    frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, 120_Hz, {});
-    ASSERT_EQ(1, frameRateOverrides.size());
-    ASSERT_EQ(1, frameRateOverrides.count(1234));
-    ASSERT_EQ(60_Hz, frameRateOverrides.at(1234));
+    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
 
-    frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, 120_Hz, {.touch = true});
-    ASSERT_EQ(1, frameRateOverrides.size());
-    ASSERT_EQ(1, frameRateOverrides.count(1234));
-    ASSERT_EQ(60_Hz, frameRateOverrides.at(1234));
+    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
 
     layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
-    frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, 120_Hz, {});
-    ASSERT_EQ(1, frameRateOverrides.size());
-    ASSERT_EQ(1, frameRateOverrides.count(1234));
-    ASSERT_EQ(60_Hz, frameRateOverrides.at(1234));
+    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
 
-    frameRateOverrides = refreshRateConfigs->getFrameRateOverrides(layers, 120_Hz, {.touch = true});
-    ASSERT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_TRUE(frameRateOverrides.empty());
 }
 
 } // namespace
 } // namespace android::scheduler
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wextra"
diff --git a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
index 5ac5812..39dbb07 100644
--- a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
@@ -91,10 +91,9 @@
     flush(secondTransactionSetVsyncId);
 
     proto::TransactionTraceFile proto = writeToProto();
-    EXPECT_EQ(proto.entry().size(), 3);
-    // skip starting entry
-    verifyEntry(proto.entry(1), firstTransactionSet, firstTransactionSetVsyncId);
-    verifyEntry(proto.entry(2), secondTransactionSet, secondTransactionSetVsyncId);
+    EXPECT_EQ(proto.entry().size(), 2);
+    verifyEntry(proto.entry(0), firstTransactionSet, firstTransactionSetVsyncId);
+    verifyEntry(proto.entry(1), secondTransactionSet, secondTransactionSetVsyncId);
 }
 
 class TransactionTracingLayerHandlingTest : public TransactionTracingTest {
@@ -274,15 +273,13 @@
 TEST_F(TransactionTracingMirrorLayerTest, canAddMirrorLayers) {
     proto::TransactionTraceFile proto = writeToProto();
     // We don't have any starting states since no layer was removed from.
-    EXPECT_EQ(proto.entry().size(), 2);
-    EXPECT_EQ(proto.entry(0).transactions().size(), 0);
-    EXPECT_EQ(proto.entry(0).added_layers().size(), 0);
+    EXPECT_EQ(proto.entry().size(), 1);
 
     // Verify the mirror layer was added
-    EXPECT_EQ(proto.entry(1).transactions().size(), 1);
-    EXPECT_EQ(proto.entry(1).added_layers().size(), 2);
-    EXPECT_EQ(proto.entry(1).added_layers(1).layer_id(), mMirrorLayerId);
-    EXPECT_EQ(proto.entry(1).transactions(0).layer_changes().size(), 2);
-    EXPECT_EQ(proto.entry(1).transactions(0).layer_changes(1).z(), 43);
+    EXPECT_EQ(proto.entry(0).transactions().size(), 1);
+    EXPECT_EQ(proto.entry(0).added_layers().size(), 2);
+    EXPECT_EQ(proto.entry(0).added_layers(1).layer_id(), mMirrorLayerId);
+    EXPECT_EQ(proto.entry(0).transactions(0).layer_changes().size(), 2);
+    EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(1).z(), 43);
 }
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 8a420ef..0765d5b 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -19,6 +19,7 @@
 #include <gmock/gmock.h>
 
 #include "DisplayHardware/ComposerHal.h"
+#include "DisplayHardware/HWC2.h"
 
 namespace android {
 
@@ -51,7 +52,7 @@
     MOCK_METHOD(bool, isSupported, (OptionalFeature), (const, override));
     MOCK_METHOD0(getCapabilities, std::vector<IComposer::Capability>());
     MOCK_METHOD0(dumpDebugInfo, std::string());
-    MOCK_METHOD1(registerCallback, void(const sp<IComposerCallback>&));
+    MOCK_METHOD1(registerCallback, void(HWC2::ComposerCallback&));
     MOCK_METHOD0(resetCommands, void());
     MOCK_METHOD0(executeCommands, Error());
     MOCK_METHOD0(getMaxVirtualDisplayCount, uint32_t());
diff --git a/vulkan/libvulkan/api.cpp b/vulkan/libvulkan/api.cpp
index fa3b260..c335e2a 100644
--- a/vulkan/libvulkan/api.cpp
+++ b/vulkan/libvulkan/api.cpp
@@ -1473,7 +1473,7 @@
     if (!EnsureInitialized())
         return VK_ERROR_OUT_OF_HOST_MEMORY;
 
-    *pApiVersion = VK_API_VERSION_1_1;
+    *pApiVersion = VK_API_VERSION_1_3;
     return VK_SUCCESS;
 }
 
diff --git a/vulkan/libvulkan/api_gen.cpp b/vulkan/libvulkan/api_gen.cpp
index 33401d2..df70bf4 100644
--- a/vulkan/libvulkan/api_gen.cpp
+++ b/vulkan/libvulkan/api_gen.cpp
@@ -179,6 +179,7 @@
     INIT_PROC(false, instance, GetPhysicalDeviceExternalFenceProperties);
     INIT_PROC(false, instance, EnumeratePhysicalDeviceGroups);
     INIT_PROC_EXT(KHR_swapchain, false, instance, GetPhysicalDevicePresentRectanglesKHR);
+    INIT_PROC(false, instance, GetPhysicalDeviceToolProperties);
     // clang-format on
 
     return success;
@@ -334,6 +335,9 @@
     INIT_PROC(false, dev, GetBufferMemoryRequirements2);
     INIT_PROC(false, dev, GetImageMemoryRequirements2);
     INIT_PROC(false, dev, GetImageSparseMemoryRequirements2);
+    INIT_PROC(false, dev, GetDeviceBufferMemoryRequirements);
+    INIT_PROC(false, dev, GetDeviceImageMemoryRequirements);
+    INIT_PROC(false, dev, GetDeviceImageSparseMemoryRequirements);
     INIT_PROC(false, dev, CreateSamplerYcbcrConversion);
     INIT_PROC(false, dev, DestroySamplerYcbcrConversion);
     INIT_PROC(false, dev, GetDeviceQueue2);
@@ -352,6 +356,39 @@
     INIT_PROC(false, dev, GetBufferOpaqueCaptureAddress);
     INIT_PROC(false, dev, GetBufferDeviceAddress);
     INIT_PROC(false, dev, GetDeviceMemoryOpaqueCaptureAddress);
+    INIT_PROC(false, dev, CmdSetCullMode);
+    INIT_PROC(false, dev, CmdSetFrontFace);
+    INIT_PROC(false, dev, CmdSetPrimitiveTopology);
+    INIT_PROC(false, dev, CmdSetViewportWithCount);
+    INIT_PROC(false, dev, CmdSetScissorWithCount);
+    INIT_PROC(false, dev, CmdBindVertexBuffers2);
+    INIT_PROC(false, dev, CmdSetDepthTestEnable);
+    INIT_PROC(false, dev, CmdSetDepthWriteEnable);
+    INIT_PROC(false, dev, CmdSetDepthCompareOp);
+    INIT_PROC(false, dev, CmdSetDepthBoundsTestEnable);
+    INIT_PROC(false, dev, CmdSetStencilTestEnable);
+    INIT_PROC(false, dev, CmdSetStencilOp);
+    INIT_PROC(false, dev, CmdSetRasterizerDiscardEnable);
+    INIT_PROC(false, dev, CmdSetDepthBiasEnable);
+    INIT_PROC(false, dev, CmdSetPrimitiveRestartEnable);
+    INIT_PROC(false, dev, CreatePrivateDataSlot);
+    INIT_PROC(false, dev, DestroyPrivateDataSlot);
+    INIT_PROC(false, dev, SetPrivateData);
+    INIT_PROC(false, dev, GetPrivateData);
+    INIT_PROC(false, dev, CmdCopyBuffer2);
+    INIT_PROC(false, dev, CmdCopyImage2);
+    INIT_PROC(false, dev, CmdBlitImage2);
+    INIT_PROC(false, dev, CmdCopyBufferToImage2);
+    INIT_PROC(false, dev, CmdCopyImageToBuffer2);
+    INIT_PROC(false, dev, CmdResolveImage2);
+    INIT_PROC(false, dev, CmdSetEvent2);
+    INIT_PROC(false, dev, CmdResetEvent2);
+    INIT_PROC(false, dev, CmdWaitEvents2);
+    INIT_PROC(false, dev, CmdPipelineBarrier2);
+    INIT_PROC(false, dev, QueueSubmit2);
+    INIT_PROC(false, dev, CmdWriteTimestamp2);
+    INIT_PROC(false, dev, CmdBeginRendering);
+    INIT_PROC(false, dev, CmdEndRendering);
     // clang-format on
 
     return success;
@@ -530,6 +567,9 @@
 VKAPI_ATTR void GetBufferMemoryRequirements2(VkDevice device, const VkBufferMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
 VKAPI_ATTR void GetImageMemoryRequirements2(VkDevice device, const VkImageMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
 VKAPI_ATTR void GetImageSparseMemoryRequirements2(VkDevice device, const VkImageSparseMemoryRequirementsInfo2* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements);
+VKAPI_ATTR void GetDeviceBufferMemoryRequirements(VkDevice device, const VkDeviceBufferMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements);
+VKAPI_ATTR void GetDeviceImageMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements);
+VKAPI_ATTR void GetDeviceImageSparseMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements);
 VKAPI_ATTR VkResult CreateSamplerYcbcrConversion(VkDevice device, const VkSamplerYcbcrConversionCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSamplerYcbcrConversion* pYcbcrConversion);
 VKAPI_ATTR void DestroySamplerYcbcrConversion(VkDevice device, VkSamplerYcbcrConversion ycbcrConversion, const VkAllocationCallbacks* pAllocator);
 VKAPI_ATTR void GetDeviceQueue2(VkDevice device, const VkDeviceQueueInfo2* pQueueInfo, VkQueue* pQueue);
@@ -548,6 +588,40 @@
 VKAPI_ATTR uint64_t GetBufferOpaqueCaptureAddress(VkDevice device, const VkBufferDeviceAddressInfo* pInfo);
 VKAPI_ATTR VkDeviceAddress GetBufferDeviceAddress(VkDevice device, const VkBufferDeviceAddressInfo* pInfo);
 VKAPI_ATTR uint64_t GetDeviceMemoryOpaqueCaptureAddress(VkDevice device, const VkDeviceMemoryOpaqueCaptureAddressInfo* pInfo);
+VKAPI_ATTR VkResult GetPhysicalDeviceToolProperties(VkPhysicalDevice physicalDevice, uint32_t* pToolCount, VkPhysicalDeviceToolProperties* pToolProperties);
+VKAPI_ATTR void CmdSetCullMode(VkCommandBuffer commandBuffer, VkCullModeFlags cullMode);
+VKAPI_ATTR void CmdSetFrontFace(VkCommandBuffer commandBuffer, VkFrontFace frontFace);
+VKAPI_ATTR void CmdSetPrimitiveTopology(VkCommandBuffer commandBuffer, VkPrimitiveTopology primitiveTopology);
+VKAPI_ATTR void CmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_t viewportCount, const VkViewport* pViewports);
+VKAPI_ATTR void CmdSetScissorWithCount(VkCommandBuffer commandBuffer, uint32_t scissorCount, const VkRect2D* pScissors);
+VKAPI_ATTR void CmdBindVertexBuffers2(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes, const VkDeviceSize* pStrides);
+VKAPI_ATTR void CmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable);
+VKAPI_ATTR void CmdSetDepthWriteEnable(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable);
+VKAPI_ATTR void CmdSetDepthCompareOp(VkCommandBuffer commandBuffer, VkCompareOp depthCompareOp);
+VKAPI_ATTR void CmdSetDepthBoundsTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthBoundsTestEnable);
+VKAPI_ATTR void CmdSetStencilTestEnable(VkCommandBuffer commandBuffer, VkBool32 stencilTestEnable);
+VKAPI_ATTR void CmdSetStencilOp(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, VkStencilOp failOp, VkStencilOp passOp, VkStencilOp depthFailOp, VkCompareOp compareOp);
+VKAPI_ATTR void CmdSetRasterizerDiscardEnable(VkCommandBuffer commandBuffer, VkBool32 rasterizerDiscardEnable);
+VKAPI_ATTR void CmdSetDepthBiasEnable(VkCommandBuffer commandBuffer, VkBool32 depthBiasEnable);
+VKAPI_ATTR void CmdSetPrimitiveRestartEnable(VkCommandBuffer commandBuffer, VkBool32 primitiveRestartEnable);
+VKAPI_ATTR VkResult CreatePrivateDataSlot(VkDevice device, const VkPrivateDataSlotCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPrivateDataSlot* pPrivateDataSlot);
+VKAPI_ATTR void DestroyPrivateDataSlot(VkDevice device, VkPrivateDataSlot privateDataSlot, const VkAllocationCallbacks* pAllocator);
+VKAPI_ATTR VkResult SetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t data);
+VKAPI_ATTR void GetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t* pData);
+VKAPI_ATTR void CmdCopyBuffer2(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2* pCopyBufferInfo);
+VKAPI_ATTR void CmdCopyImage2(VkCommandBuffer commandBuffer, const VkCopyImageInfo2* pCopyImageInfo);
+VKAPI_ATTR void CmdBlitImage2(VkCommandBuffer commandBuffer, const VkBlitImageInfo2* pBlitImageInfo);
+VKAPI_ATTR void CmdCopyBufferToImage2(VkCommandBuffer commandBuffer, const VkCopyBufferToImageInfo2* pCopyBufferToImageInfo);
+VKAPI_ATTR void CmdCopyImageToBuffer2(VkCommandBuffer commandBuffer, const VkCopyImageToBufferInfo2* pCopyImageToBufferInfo);
+VKAPI_ATTR void CmdResolveImage2(VkCommandBuffer commandBuffer, const VkResolveImageInfo2* pResolveImageInfo);
+VKAPI_ATTR void CmdSetEvent2(VkCommandBuffer commandBuffer, VkEvent event, const VkDependencyInfo* pDependencyInfo);
+VKAPI_ATTR void CmdResetEvent2(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags2 stageMask);
+VKAPI_ATTR void CmdWaitEvents2(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents, const VkDependencyInfo* pDependencyInfos);
+VKAPI_ATTR void CmdPipelineBarrier2(VkCommandBuffer commandBuffer, const VkDependencyInfo* pDependencyInfo);
+VKAPI_ATTR VkResult QueueSubmit2(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2* pSubmits, VkFence fence);
+VKAPI_ATTR void CmdWriteTimestamp2(VkCommandBuffer commandBuffer, VkPipelineStageFlags2 stage, VkQueryPool queryPool, uint32_t query);
+VKAPI_ATTR void CmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo);
+VKAPI_ATTR void CmdEndRendering(VkCommandBuffer commandBuffer);
 
 VKAPI_ATTR VkResult EnumeratePhysicalDevices(VkInstance instance, uint32_t* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices) {
     return GetData(instance).dispatch.EnumeratePhysicalDevices(instance, pPhysicalDeviceCount, pPhysicalDevices);
@@ -625,6 +699,7 @@
         "vkGetPhysicalDeviceSurfaceFormatsKHR",
         "vkGetPhysicalDeviceSurfacePresentModesKHR",
         "vkGetPhysicalDeviceSurfaceSupportKHR",
+        "vkGetPhysicalDeviceToolProperties",
         "vkGetPhysicalDeviceToolPropertiesEXT",
         "vkGetPhysicalDeviceVideoCapabilitiesKHR",
         "vkGetPhysicalDeviceVideoFormatPropertiesKHR",
@@ -680,18 +755,25 @@
         { "vkCmdBeginQuery", reinterpret_cast<PFN_vkVoidFunction>(CmdBeginQuery) },
         { "vkCmdBeginRenderPass", reinterpret_cast<PFN_vkVoidFunction>(CmdBeginRenderPass) },
         { "vkCmdBeginRenderPass2", reinterpret_cast<PFN_vkVoidFunction>(CmdBeginRenderPass2) },
+        { "vkCmdBeginRendering", reinterpret_cast<PFN_vkVoidFunction>(CmdBeginRendering) },
         { "vkCmdBindDescriptorSets", reinterpret_cast<PFN_vkVoidFunction>(CmdBindDescriptorSets) },
         { "vkCmdBindIndexBuffer", reinterpret_cast<PFN_vkVoidFunction>(CmdBindIndexBuffer) },
         { "vkCmdBindPipeline", reinterpret_cast<PFN_vkVoidFunction>(CmdBindPipeline) },
         { "vkCmdBindVertexBuffers", reinterpret_cast<PFN_vkVoidFunction>(CmdBindVertexBuffers) },
+        { "vkCmdBindVertexBuffers2", reinterpret_cast<PFN_vkVoidFunction>(CmdBindVertexBuffers2) },
         { "vkCmdBlitImage", reinterpret_cast<PFN_vkVoidFunction>(CmdBlitImage) },
+        { "vkCmdBlitImage2", reinterpret_cast<PFN_vkVoidFunction>(CmdBlitImage2) },
         { "vkCmdClearAttachments", reinterpret_cast<PFN_vkVoidFunction>(CmdClearAttachments) },
         { "vkCmdClearColorImage", reinterpret_cast<PFN_vkVoidFunction>(CmdClearColorImage) },
         { "vkCmdClearDepthStencilImage", reinterpret_cast<PFN_vkVoidFunction>(CmdClearDepthStencilImage) },
         { "vkCmdCopyBuffer", reinterpret_cast<PFN_vkVoidFunction>(CmdCopyBuffer) },
+        { "vkCmdCopyBuffer2", reinterpret_cast<PFN_vkVoidFunction>(CmdCopyBuffer2) },
         { "vkCmdCopyBufferToImage", reinterpret_cast<PFN_vkVoidFunction>(CmdCopyBufferToImage) },
+        { "vkCmdCopyBufferToImage2", reinterpret_cast<PFN_vkVoidFunction>(CmdCopyBufferToImage2) },
         { "vkCmdCopyImage", reinterpret_cast<PFN_vkVoidFunction>(CmdCopyImage) },
+        { "vkCmdCopyImage2", reinterpret_cast<PFN_vkVoidFunction>(CmdCopyImage2) },
         { "vkCmdCopyImageToBuffer", reinterpret_cast<PFN_vkVoidFunction>(CmdCopyImageToBuffer) },
+        { "vkCmdCopyImageToBuffer2", reinterpret_cast<PFN_vkVoidFunction>(CmdCopyImageToBuffer2) },
         { "vkCmdCopyQueryPoolResults", reinterpret_cast<PFN_vkVoidFunction>(CmdCopyQueryPoolResults) },
         { "vkCmdDispatch", reinterpret_cast<PFN_vkVoidFunction>(CmdDispatch) },
         { "vkCmdDispatchBase", reinterpret_cast<PFN_vkVoidFunction>(CmdDispatchBase) },
@@ -705,29 +787,50 @@
         { "vkCmdEndQuery", reinterpret_cast<PFN_vkVoidFunction>(CmdEndQuery) },
         { "vkCmdEndRenderPass", reinterpret_cast<PFN_vkVoidFunction>(CmdEndRenderPass) },
         { "vkCmdEndRenderPass2", reinterpret_cast<PFN_vkVoidFunction>(CmdEndRenderPass2) },
+        { "vkCmdEndRendering", reinterpret_cast<PFN_vkVoidFunction>(CmdEndRendering) },
         { "vkCmdExecuteCommands", reinterpret_cast<PFN_vkVoidFunction>(CmdExecuteCommands) },
         { "vkCmdFillBuffer", reinterpret_cast<PFN_vkVoidFunction>(CmdFillBuffer) },
         { "vkCmdNextSubpass", reinterpret_cast<PFN_vkVoidFunction>(CmdNextSubpass) },
         { "vkCmdNextSubpass2", reinterpret_cast<PFN_vkVoidFunction>(CmdNextSubpass2) },
         { "vkCmdPipelineBarrier", reinterpret_cast<PFN_vkVoidFunction>(CmdPipelineBarrier) },
+        { "vkCmdPipelineBarrier2", reinterpret_cast<PFN_vkVoidFunction>(CmdPipelineBarrier2) },
         { "vkCmdPushConstants", reinterpret_cast<PFN_vkVoidFunction>(CmdPushConstants) },
         { "vkCmdResetEvent", reinterpret_cast<PFN_vkVoidFunction>(CmdResetEvent) },
+        { "vkCmdResetEvent2", reinterpret_cast<PFN_vkVoidFunction>(CmdResetEvent2) },
         { "vkCmdResetQueryPool", reinterpret_cast<PFN_vkVoidFunction>(CmdResetQueryPool) },
         { "vkCmdResolveImage", reinterpret_cast<PFN_vkVoidFunction>(CmdResolveImage) },
+        { "vkCmdResolveImage2", reinterpret_cast<PFN_vkVoidFunction>(CmdResolveImage2) },
         { "vkCmdSetBlendConstants", reinterpret_cast<PFN_vkVoidFunction>(CmdSetBlendConstants) },
+        { "vkCmdSetCullMode", reinterpret_cast<PFN_vkVoidFunction>(CmdSetCullMode) },
         { "vkCmdSetDepthBias", reinterpret_cast<PFN_vkVoidFunction>(CmdSetDepthBias) },
+        { "vkCmdSetDepthBiasEnable", reinterpret_cast<PFN_vkVoidFunction>(CmdSetDepthBiasEnable) },
         { "vkCmdSetDepthBounds", reinterpret_cast<PFN_vkVoidFunction>(CmdSetDepthBounds) },
+        { "vkCmdSetDepthBoundsTestEnable", reinterpret_cast<PFN_vkVoidFunction>(CmdSetDepthBoundsTestEnable) },
+        { "vkCmdSetDepthCompareOp", reinterpret_cast<PFN_vkVoidFunction>(CmdSetDepthCompareOp) },
+        { "vkCmdSetDepthTestEnable", reinterpret_cast<PFN_vkVoidFunction>(CmdSetDepthTestEnable) },
+        { "vkCmdSetDepthWriteEnable", reinterpret_cast<PFN_vkVoidFunction>(CmdSetDepthWriteEnable) },
         { "vkCmdSetDeviceMask", reinterpret_cast<PFN_vkVoidFunction>(CmdSetDeviceMask) },
         { "vkCmdSetEvent", reinterpret_cast<PFN_vkVoidFunction>(CmdSetEvent) },
+        { "vkCmdSetEvent2", reinterpret_cast<PFN_vkVoidFunction>(CmdSetEvent2) },
+        { "vkCmdSetFrontFace", reinterpret_cast<PFN_vkVoidFunction>(CmdSetFrontFace) },
         { "vkCmdSetLineWidth", reinterpret_cast<PFN_vkVoidFunction>(CmdSetLineWidth) },
+        { "vkCmdSetPrimitiveRestartEnable", reinterpret_cast<PFN_vkVoidFunction>(CmdSetPrimitiveRestartEnable) },
+        { "vkCmdSetPrimitiveTopology", reinterpret_cast<PFN_vkVoidFunction>(CmdSetPrimitiveTopology) },
+        { "vkCmdSetRasterizerDiscardEnable", reinterpret_cast<PFN_vkVoidFunction>(CmdSetRasterizerDiscardEnable) },
         { "vkCmdSetScissor", reinterpret_cast<PFN_vkVoidFunction>(CmdSetScissor) },
+        { "vkCmdSetScissorWithCount", reinterpret_cast<PFN_vkVoidFunction>(CmdSetScissorWithCount) },
         { "vkCmdSetStencilCompareMask", reinterpret_cast<PFN_vkVoidFunction>(CmdSetStencilCompareMask) },
+        { "vkCmdSetStencilOp", reinterpret_cast<PFN_vkVoidFunction>(CmdSetStencilOp) },
         { "vkCmdSetStencilReference", reinterpret_cast<PFN_vkVoidFunction>(CmdSetStencilReference) },
+        { "vkCmdSetStencilTestEnable", reinterpret_cast<PFN_vkVoidFunction>(CmdSetStencilTestEnable) },
         { "vkCmdSetStencilWriteMask", reinterpret_cast<PFN_vkVoidFunction>(CmdSetStencilWriteMask) },
         { "vkCmdSetViewport", reinterpret_cast<PFN_vkVoidFunction>(CmdSetViewport) },
+        { "vkCmdSetViewportWithCount", reinterpret_cast<PFN_vkVoidFunction>(CmdSetViewportWithCount) },
         { "vkCmdUpdateBuffer", reinterpret_cast<PFN_vkVoidFunction>(CmdUpdateBuffer) },
         { "vkCmdWaitEvents", reinterpret_cast<PFN_vkVoidFunction>(CmdWaitEvents) },
+        { "vkCmdWaitEvents2", reinterpret_cast<PFN_vkVoidFunction>(CmdWaitEvents2) },
         { "vkCmdWriteTimestamp", reinterpret_cast<PFN_vkVoidFunction>(CmdWriteTimestamp) },
+        { "vkCmdWriteTimestamp2", reinterpret_cast<PFN_vkVoidFunction>(CmdWriteTimestamp2) },
         { "vkCreateBuffer", reinterpret_cast<PFN_vkVoidFunction>(CreateBuffer) },
         { "vkCreateBufferView", reinterpret_cast<PFN_vkVoidFunction>(CreateBufferView) },
         { "vkCreateCommandPool", reinterpret_cast<PFN_vkVoidFunction>(CreateCommandPool) },
@@ -745,6 +848,7 @@
         { "vkCreateInstance", nullptr },
         { "vkCreatePipelineCache", reinterpret_cast<PFN_vkVoidFunction>(CreatePipelineCache) },
         { "vkCreatePipelineLayout", reinterpret_cast<PFN_vkVoidFunction>(CreatePipelineLayout) },
+        { "vkCreatePrivateDataSlot", reinterpret_cast<PFN_vkVoidFunction>(CreatePrivateDataSlot) },
         { "vkCreateQueryPool", reinterpret_cast<PFN_vkVoidFunction>(CreateQueryPool) },
         { "vkCreateRenderPass", reinterpret_cast<PFN_vkVoidFunction>(CreateRenderPass) },
         { "vkCreateRenderPass2", reinterpret_cast<PFN_vkVoidFunction>(CreateRenderPass2) },
@@ -769,6 +873,7 @@
         { "vkDestroyPipeline", reinterpret_cast<PFN_vkVoidFunction>(DestroyPipeline) },
         { "vkDestroyPipelineCache", reinterpret_cast<PFN_vkVoidFunction>(DestroyPipelineCache) },
         { "vkDestroyPipelineLayout", reinterpret_cast<PFN_vkVoidFunction>(DestroyPipelineLayout) },
+        { "vkDestroyPrivateDataSlot", reinterpret_cast<PFN_vkVoidFunction>(DestroyPrivateDataSlot) },
         { "vkDestroyQueryPool", reinterpret_cast<PFN_vkVoidFunction>(DestroyQueryPool) },
         { "vkDestroyRenderPass", reinterpret_cast<PFN_vkVoidFunction>(DestroyRenderPass) },
         { "vkDestroySampler", reinterpret_cast<PFN_vkVoidFunction>(DestroySampler) },
@@ -793,9 +898,12 @@
         { "vkGetBufferMemoryRequirements2", reinterpret_cast<PFN_vkVoidFunction>(GetBufferMemoryRequirements2) },
         { "vkGetBufferOpaqueCaptureAddress", reinterpret_cast<PFN_vkVoidFunction>(GetBufferOpaqueCaptureAddress) },
         { "vkGetDescriptorSetLayoutSupport", reinterpret_cast<PFN_vkVoidFunction>(GetDescriptorSetLayoutSupport) },
+        { "vkGetDeviceBufferMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceBufferMemoryRequirements) },
         { "vkGetDeviceGroupPeerMemoryFeatures", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceGroupPeerMemoryFeatures) },
         { "vkGetDeviceGroupPresentCapabilitiesKHR", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceGroupPresentCapabilitiesKHR) },
         { "vkGetDeviceGroupSurfacePresentModesKHR", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceGroupSurfacePresentModesKHR) },
+        { "vkGetDeviceImageMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceImageMemoryRequirements) },
+        { "vkGetDeviceImageSparseMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceImageSparseMemoryRequirements) },
         { "vkGetDeviceMemoryCommitment", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceMemoryCommitment) },
         { "vkGetDeviceMemoryOpaqueCaptureAddress", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceMemoryOpaqueCaptureAddress) },
         { "vkGetDeviceProcAddr", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceProcAddr) },
@@ -811,6 +919,7 @@
         { "vkGetInstanceProcAddr", reinterpret_cast<PFN_vkVoidFunction>(GetInstanceProcAddr) },
         { "vkGetMemoryAndroidHardwareBufferANDROID", reinterpret_cast<PFN_vkVoidFunction>(GetMemoryAndroidHardwareBufferANDROID) },
         { "vkGetPipelineCacheData", reinterpret_cast<PFN_vkVoidFunction>(GetPipelineCacheData) },
+        { "vkGetPrivateData", reinterpret_cast<PFN_vkVoidFunction>(GetPrivateData) },
         { "vkGetQueryPoolResults", reinterpret_cast<PFN_vkVoidFunction>(GetQueryPoolResults) },
         { "vkGetRenderAreaGranularity", reinterpret_cast<PFN_vkVoidFunction>(GetRenderAreaGranularity) },
         { "vkGetSemaphoreCounterValue", reinterpret_cast<PFN_vkVoidFunction>(GetSemaphoreCounterValue) },
@@ -821,6 +930,7 @@
         { "vkQueueBindSparse", reinterpret_cast<PFN_vkVoidFunction>(QueueBindSparse) },
         { "vkQueuePresentKHR", reinterpret_cast<PFN_vkVoidFunction>(QueuePresentKHR) },
         { "vkQueueSubmit", reinterpret_cast<PFN_vkVoidFunction>(QueueSubmit) },
+        { "vkQueueSubmit2", reinterpret_cast<PFN_vkVoidFunction>(QueueSubmit2) },
         { "vkQueueWaitIdle", reinterpret_cast<PFN_vkVoidFunction>(QueueWaitIdle) },
         { "vkResetCommandBuffer", reinterpret_cast<PFN_vkVoidFunction>(ResetCommandBuffer) },
         { "vkResetCommandPool", reinterpret_cast<PFN_vkVoidFunction>(ResetCommandPool) },
@@ -829,6 +939,7 @@
         { "vkResetFences", reinterpret_cast<PFN_vkVoidFunction>(ResetFences) },
         { "vkResetQueryPool", reinterpret_cast<PFN_vkVoidFunction>(ResetQueryPool) },
         { "vkSetEvent", reinterpret_cast<PFN_vkVoidFunction>(SetEvent) },
+        { "vkSetPrivateData", reinterpret_cast<PFN_vkVoidFunction>(SetPrivateData) },
         { "vkSignalSemaphore", reinterpret_cast<PFN_vkVoidFunction>(SignalSemaphore) },
         { "vkTrimCommandPool", reinterpret_cast<PFN_vkVoidFunction>(TrimCommandPool) },
         { "vkUnmapMemory", reinterpret_cast<PFN_vkVoidFunction>(UnmapMemory) },
@@ -1515,6 +1626,18 @@
     GetData(device).dispatch.GetImageSparseMemoryRequirements2(device, pInfo, pSparseMemoryRequirementCount, pSparseMemoryRequirements);
 }
 
+VKAPI_ATTR void GetDeviceBufferMemoryRequirements(VkDevice device, const VkDeviceBufferMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements) {
+    GetData(device).dispatch.GetDeviceBufferMemoryRequirements(device, pInfo, pMemoryRequirements);
+}
+
+VKAPI_ATTR void GetDeviceImageMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements) {
+    GetData(device).dispatch.GetDeviceImageMemoryRequirements(device, pInfo, pMemoryRequirements);
+}
+
+VKAPI_ATTR void GetDeviceImageSparseMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements) {
+    GetData(device).dispatch.GetDeviceImageSparseMemoryRequirements(device, pInfo, pSparseMemoryRequirementCount, pSparseMemoryRequirements);
+}
+
 VKAPI_ATTR VkResult CreateSamplerYcbcrConversion(VkDevice device, const VkSamplerYcbcrConversionCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSamplerYcbcrConversion* pYcbcrConversion) {
     return GetData(device).dispatch.CreateSamplerYcbcrConversion(device, pCreateInfo, pAllocator, pYcbcrConversion);
 }
@@ -1587,6 +1710,142 @@
     return GetData(device).dispatch.GetDeviceMemoryOpaqueCaptureAddress(device, pInfo);
 }
 
+VKAPI_ATTR VkResult GetPhysicalDeviceToolProperties(VkPhysicalDevice physicalDevice, uint32_t* pToolCount, VkPhysicalDeviceToolProperties* pToolProperties) {
+    return GetData(physicalDevice).dispatch.GetPhysicalDeviceToolProperties(physicalDevice, pToolCount, pToolProperties);
+}
+
+VKAPI_ATTR void CmdSetCullMode(VkCommandBuffer commandBuffer, VkCullModeFlags cullMode) {
+    GetData(commandBuffer).dispatch.CmdSetCullMode(commandBuffer, cullMode);
+}
+
+VKAPI_ATTR void CmdSetFrontFace(VkCommandBuffer commandBuffer, VkFrontFace frontFace) {
+    GetData(commandBuffer).dispatch.CmdSetFrontFace(commandBuffer, frontFace);
+}
+
+VKAPI_ATTR void CmdSetPrimitiveTopology(VkCommandBuffer commandBuffer, VkPrimitiveTopology primitiveTopology) {
+    GetData(commandBuffer).dispatch.CmdSetPrimitiveTopology(commandBuffer, primitiveTopology);
+}
+
+VKAPI_ATTR void CmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_t viewportCount, const VkViewport* pViewports) {
+    GetData(commandBuffer).dispatch.CmdSetViewportWithCount(commandBuffer, viewportCount, pViewports);
+}
+
+VKAPI_ATTR void CmdSetScissorWithCount(VkCommandBuffer commandBuffer, uint32_t scissorCount, const VkRect2D* pScissors) {
+    GetData(commandBuffer).dispatch.CmdSetScissorWithCount(commandBuffer, scissorCount, pScissors);
+}
+
+VKAPI_ATTR void CmdBindVertexBuffers2(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes, const VkDeviceSize* pStrides) {
+    GetData(commandBuffer).dispatch.CmdBindVertexBuffers2(commandBuffer, firstBinding, bindingCount, pBuffers, pOffsets, pSizes, pStrides);
+}
+
+VKAPI_ATTR void CmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable) {
+    GetData(commandBuffer).dispatch.CmdSetDepthTestEnable(commandBuffer, depthTestEnable);
+}
+
+VKAPI_ATTR void CmdSetDepthWriteEnable(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable) {
+    GetData(commandBuffer).dispatch.CmdSetDepthWriteEnable(commandBuffer, depthWriteEnable);
+}
+
+VKAPI_ATTR void CmdSetDepthCompareOp(VkCommandBuffer commandBuffer, VkCompareOp depthCompareOp) {
+    GetData(commandBuffer).dispatch.CmdSetDepthCompareOp(commandBuffer, depthCompareOp);
+}
+
+VKAPI_ATTR void CmdSetDepthBoundsTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthBoundsTestEnable) {
+    GetData(commandBuffer).dispatch.CmdSetDepthBoundsTestEnable(commandBuffer, depthBoundsTestEnable);
+}
+
+VKAPI_ATTR void CmdSetStencilTestEnable(VkCommandBuffer commandBuffer, VkBool32 stencilTestEnable) {
+    GetData(commandBuffer).dispatch.CmdSetStencilTestEnable(commandBuffer, stencilTestEnable);
+}
+
+VKAPI_ATTR void CmdSetStencilOp(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, VkStencilOp failOp, VkStencilOp passOp, VkStencilOp depthFailOp, VkCompareOp compareOp) {
+    GetData(commandBuffer).dispatch.CmdSetStencilOp(commandBuffer, faceMask, failOp, passOp, depthFailOp, compareOp);
+}
+
+VKAPI_ATTR void CmdSetRasterizerDiscardEnable(VkCommandBuffer commandBuffer, VkBool32 rasterizerDiscardEnable) {
+    GetData(commandBuffer).dispatch.CmdSetRasterizerDiscardEnable(commandBuffer, rasterizerDiscardEnable);
+}
+
+VKAPI_ATTR void CmdSetDepthBiasEnable(VkCommandBuffer commandBuffer, VkBool32 depthBiasEnable) {
+    GetData(commandBuffer).dispatch.CmdSetDepthBiasEnable(commandBuffer, depthBiasEnable);
+}
+
+VKAPI_ATTR void CmdSetPrimitiveRestartEnable(VkCommandBuffer commandBuffer, VkBool32 primitiveRestartEnable) {
+    GetData(commandBuffer).dispatch.CmdSetPrimitiveRestartEnable(commandBuffer, primitiveRestartEnable);
+}
+
+VKAPI_ATTR VkResult CreatePrivateDataSlot(VkDevice device, const VkPrivateDataSlotCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPrivateDataSlot* pPrivateDataSlot) {
+    return GetData(device).dispatch.CreatePrivateDataSlot(device, pCreateInfo, pAllocator, pPrivateDataSlot);
+}
+
+VKAPI_ATTR void DestroyPrivateDataSlot(VkDevice device, VkPrivateDataSlot privateDataSlot, const VkAllocationCallbacks* pAllocator) {
+    GetData(device).dispatch.DestroyPrivateDataSlot(device, privateDataSlot, pAllocator);
+}
+
+VKAPI_ATTR VkResult SetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t data) {
+    return GetData(device).dispatch.SetPrivateData(device, objectType, objectHandle, privateDataSlot, data);
+}
+
+VKAPI_ATTR void GetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t* pData) {
+    GetData(device).dispatch.GetPrivateData(device, objectType, objectHandle, privateDataSlot, pData);
+}
+
+VKAPI_ATTR void CmdCopyBuffer2(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2* pCopyBufferInfo) {
+    GetData(commandBuffer).dispatch.CmdCopyBuffer2(commandBuffer, pCopyBufferInfo);
+}
+
+VKAPI_ATTR void CmdCopyImage2(VkCommandBuffer commandBuffer, const VkCopyImageInfo2* pCopyImageInfo) {
+    GetData(commandBuffer).dispatch.CmdCopyImage2(commandBuffer, pCopyImageInfo);
+}
+
+VKAPI_ATTR void CmdBlitImage2(VkCommandBuffer commandBuffer, const VkBlitImageInfo2* pBlitImageInfo) {
+    GetData(commandBuffer).dispatch.CmdBlitImage2(commandBuffer, pBlitImageInfo);
+}
+
+VKAPI_ATTR void CmdCopyBufferToImage2(VkCommandBuffer commandBuffer, const VkCopyBufferToImageInfo2* pCopyBufferToImageInfo) {
+    GetData(commandBuffer).dispatch.CmdCopyBufferToImage2(commandBuffer, pCopyBufferToImageInfo);
+}
+
+VKAPI_ATTR void CmdCopyImageToBuffer2(VkCommandBuffer commandBuffer, const VkCopyImageToBufferInfo2* pCopyImageToBufferInfo) {
+    GetData(commandBuffer).dispatch.CmdCopyImageToBuffer2(commandBuffer, pCopyImageToBufferInfo);
+}
+
+VKAPI_ATTR void CmdResolveImage2(VkCommandBuffer commandBuffer, const VkResolveImageInfo2* pResolveImageInfo) {
+    GetData(commandBuffer).dispatch.CmdResolveImage2(commandBuffer, pResolveImageInfo);
+}
+
+VKAPI_ATTR void CmdSetEvent2(VkCommandBuffer commandBuffer, VkEvent event, const VkDependencyInfo* pDependencyInfo) {
+    GetData(commandBuffer).dispatch.CmdSetEvent2(commandBuffer, event, pDependencyInfo);
+}
+
+VKAPI_ATTR void CmdResetEvent2(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags2 stageMask) {
+    GetData(commandBuffer).dispatch.CmdResetEvent2(commandBuffer, event, stageMask);
+}
+
+VKAPI_ATTR void CmdWaitEvents2(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents, const VkDependencyInfo* pDependencyInfos) {
+    GetData(commandBuffer).dispatch.CmdWaitEvents2(commandBuffer, eventCount, pEvents, pDependencyInfos);
+}
+
+VKAPI_ATTR void CmdPipelineBarrier2(VkCommandBuffer commandBuffer, const VkDependencyInfo* pDependencyInfo) {
+    GetData(commandBuffer).dispatch.CmdPipelineBarrier2(commandBuffer, pDependencyInfo);
+}
+
+VKAPI_ATTR VkResult QueueSubmit2(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2* pSubmits, VkFence fence) {
+    return GetData(queue).dispatch.QueueSubmit2(queue, submitCount, pSubmits, fence);
+}
+
+VKAPI_ATTR void CmdWriteTimestamp2(VkCommandBuffer commandBuffer, VkPipelineStageFlags2 stage, VkQueryPool queryPool, uint32_t query) {
+    GetData(commandBuffer).dispatch.CmdWriteTimestamp2(commandBuffer, stage, queryPool, query);
+}
+
+VKAPI_ATTR void CmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo) {
+    GetData(commandBuffer).dispatch.CmdBeginRendering(commandBuffer, pRenderingInfo);
+}
+
+VKAPI_ATTR void CmdEndRendering(VkCommandBuffer commandBuffer) {
+    GetData(commandBuffer).dispatch.CmdEndRendering(commandBuffer);
+}
+
 
 }  // anonymous namespace
 
@@ -2483,6 +2742,21 @@
 }
 
 __attribute__((visibility("default")))
+VKAPI_ATTR void vkGetDeviceBufferMemoryRequirements(VkDevice device, const VkDeviceBufferMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements) {
+    vulkan::api::GetDeviceBufferMemoryRequirements(device, pInfo, pMemoryRequirements);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkGetDeviceImageMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements) {
+    vulkan::api::GetDeviceImageMemoryRequirements(device, pInfo, pMemoryRequirements);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkGetDeviceImageSparseMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements) {
+    vulkan::api::GetDeviceImageSparseMemoryRequirements(device, pInfo, pSparseMemoryRequirementCount, pSparseMemoryRequirements);
+}
+
+__attribute__((visibility("default")))
 VKAPI_ATTR VkResult vkCreateSamplerYcbcrConversion(VkDevice device, const VkSamplerYcbcrConversionCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSamplerYcbcrConversion* pYcbcrConversion) {
     return vulkan::api::CreateSamplerYcbcrConversion(device, pCreateInfo, pAllocator, pYcbcrConversion);
 }
@@ -2572,4 +2846,174 @@
     return vulkan::api::GetDeviceMemoryOpaqueCaptureAddress(device, pInfo);
 }
 
+__attribute__((visibility("default")))
+VKAPI_ATTR VkResult vkGetPhysicalDeviceToolProperties(VkPhysicalDevice physicalDevice, uint32_t* pToolCount, VkPhysicalDeviceToolProperties* pToolProperties) {
+    return vulkan::api::GetPhysicalDeviceToolProperties(physicalDevice, pToolCount, pToolProperties);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetCullMode(VkCommandBuffer commandBuffer, VkCullModeFlags cullMode) {
+    vulkan::api::CmdSetCullMode(commandBuffer, cullMode);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetFrontFace(VkCommandBuffer commandBuffer, VkFrontFace frontFace) {
+    vulkan::api::CmdSetFrontFace(commandBuffer, frontFace);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetPrimitiveTopology(VkCommandBuffer commandBuffer, VkPrimitiveTopology primitiveTopology) {
+    vulkan::api::CmdSetPrimitiveTopology(commandBuffer, primitiveTopology);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_t viewportCount, const VkViewport* pViewports) {
+    vulkan::api::CmdSetViewportWithCount(commandBuffer, viewportCount, pViewports);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetScissorWithCount(VkCommandBuffer commandBuffer, uint32_t scissorCount, const VkRect2D* pScissors) {
+    vulkan::api::CmdSetScissorWithCount(commandBuffer, scissorCount, pScissors);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdBindVertexBuffers2(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes, const VkDeviceSize* pStrides) {
+    vulkan::api::CmdBindVertexBuffers2(commandBuffer, firstBinding, bindingCount, pBuffers, pOffsets, pSizes, pStrides);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable) {
+    vulkan::api::CmdSetDepthTestEnable(commandBuffer, depthTestEnable);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetDepthWriteEnable(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable) {
+    vulkan::api::CmdSetDepthWriteEnable(commandBuffer, depthWriteEnable);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetDepthCompareOp(VkCommandBuffer commandBuffer, VkCompareOp depthCompareOp) {
+    vulkan::api::CmdSetDepthCompareOp(commandBuffer, depthCompareOp);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetDepthBoundsTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthBoundsTestEnable) {
+    vulkan::api::CmdSetDepthBoundsTestEnable(commandBuffer, depthBoundsTestEnable);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetStencilTestEnable(VkCommandBuffer commandBuffer, VkBool32 stencilTestEnable) {
+    vulkan::api::CmdSetStencilTestEnable(commandBuffer, stencilTestEnable);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetStencilOp(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, VkStencilOp failOp, VkStencilOp passOp, VkStencilOp depthFailOp, VkCompareOp compareOp) {
+    vulkan::api::CmdSetStencilOp(commandBuffer, faceMask, failOp, passOp, depthFailOp, compareOp);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetRasterizerDiscardEnable(VkCommandBuffer commandBuffer, VkBool32 rasterizerDiscardEnable) {
+    vulkan::api::CmdSetRasterizerDiscardEnable(commandBuffer, rasterizerDiscardEnable);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetDepthBiasEnable(VkCommandBuffer commandBuffer, VkBool32 depthBiasEnable) {
+    vulkan::api::CmdSetDepthBiasEnable(commandBuffer, depthBiasEnable);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetPrimitiveRestartEnable(VkCommandBuffer commandBuffer, VkBool32 primitiveRestartEnable) {
+    vulkan::api::CmdSetPrimitiveRestartEnable(commandBuffer, primitiveRestartEnable);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR VkResult vkCreatePrivateDataSlot(VkDevice device, const VkPrivateDataSlotCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPrivateDataSlot* pPrivateDataSlot) {
+    return vulkan::api::CreatePrivateDataSlot(device, pCreateInfo, pAllocator, pPrivateDataSlot);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkDestroyPrivateDataSlot(VkDevice device, VkPrivateDataSlot privateDataSlot, const VkAllocationCallbacks* pAllocator) {
+    vulkan::api::DestroyPrivateDataSlot(device, privateDataSlot, pAllocator);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR VkResult vkSetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t data) {
+    return vulkan::api::SetPrivateData(device, objectType, objectHandle, privateDataSlot, data);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkGetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t* pData) {
+    vulkan::api::GetPrivateData(device, objectType, objectHandle, privateDataSlot, pData);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdCopyBuffer2(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2* pCopyBufferInfo) {
+    vulkan::api::CmdCopyBuffer2(commandBuffer, pCopyBufferInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdCopyImage2(VkCommandBuffer commandBuffer, const VkCopyImageInfo2* pCopyImageInfo) {
+    vulkan::api::CmdCopyImage2(commandBuffer, pCopyImageInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdBlitImage2(VkCommandBuffer commandBuffer, const VkBlitImageInfo2* pBlitImageInfo) {
+    vulkan::api::CmdBlitImage2(commandBuffer, pBlitImageInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdCopyBufferToImage2(VkCommandBuffer commandBuffer, const VkCopyBufferToImageInfo2* pCopyBufferToImageInfo) {
+    vulkan::api::CmdCopyBufferToImage2(commandBuffer, pCopyBufferToImageInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdCopyImageToBuffer2(VkCommandBuffer commandBuffer, const VkCopyImageToBufferInfo2* pCopyImageToBufferInfo) {
+    vulkan::api::CmdCopyImageToBuffer2(commandBuffer, pCopyImageToBufferInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdResolveImage2(VkCommandBuffer commandBuffer, const VkResolveImageInfo2* pResolveImageInfo) {
+    vulkan::api::CmdResolveImage2(commandBuffer, pResolveImageInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetEvent2(VkCommandBuffer commandBuffer, VkEvent event, const VkDependencyInfo* pDependencyInfo) {
+    vulkan::api::CmdSetEvent2(commandBuffer, event, pDependencyInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdResetEvent2(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags2 stageMask) {
+    vulkan::api::CmdResetEvent2(commandBuffer, event, stageMask);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdWaitEvents2(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents, const VkDependencyInfo* pDependencyInfos) {
+    vulkan::api::CmdWaitEvents2(commandBuffer, eventCount, pEvents, pDependencyInfos);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdPipelineBarrier2(VkCommandBuffer commandBuffer, const VkDependencyInfo* pDependencyInfo) {
+    vulkan::api::CmdPipelineBarrier2(commandBuffer, pDependencyInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR VkResult vkQueueSubmit2(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2* pSubmits, VkFence fence) {
+    return vulkan::api::QueueSubmit2(queue, submitCount, pSubmits, fence);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdWriteTimestamp2(VkCommandBuffer commandBuffer, VkPipelineStageFlags2 stage, VkQueryPool queryPool, uint32_t query) {
+    vulkan::api::CmdWriteTimestamp2(commandBuffer, stage, queryPool, query);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo) {
+    vulkan::api::CmdBeginRendering(commandBuffer, pRenderingInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdEndRendering(VkCommandBuffer commandBuffer) {
+    vulkan::api::CmdEndRendering(commandBuffer);
+}
+
 // clang-format on
diff --git a/vulkan/libvulkan/api_gen.h b/vulkan/libvulkan/api_gen.h
index ad5cc34..4998018 100644
--- a/vulkan/libvulkan/api_gen.h
+++ b/vulkan/libvulkan/api_gen.h
@@ -60,6 +60,7 @@
     PFN_vkGetPhysicalDeviceExternalFenceProperties GetPhysicalDeviceExternalFenceProperties;
     PFN_vkEnumeratePhysicalDeviceGroups EnumeratePhysicalDeviceGroups;
     PFN_vkGetPhysicalDevicePresentRectanglesKHR GetPhysicalDevicePresentRectanglesKHR;
+    PFN_vkGetPhysicalDeviceToolProperties GetPhysicalDeviceToolProperties;
     // clang-format on
 };
 
@@ -207,6 +208,9 @@
     PFN_vkGetBufferMemoryRequirements2 GetBufferMemoryRequirements2;
     PFN_vkGetImageMemoryRequirements2 GetImageMemoryRequirements2;
     PFN_vkGetImageSparseMemoryRequirements2 GetImageSparseMemoryRequirements2;
+    PFN_vkGetDeviceBufferMemoryRequirements GetDeviceBufferMemoryRequirements;
+    PFN_vkGetDeviceImageMemoryRequirements GetDeviceImageMemoryRequirements;
+    PFN_vkGetDeviceImageSparseMemoryRequirements GetDeviceImageSparseMemoryRequirements;
     PFN_vkCreateSamplerYcbcrConversion CreateSamplerYcbcrConversion;
     PFN_vkDestroySamplerYcbcrConversion DestroySamplerYcbcrConversion;
     PFN_vkGetDeviceQueue2 GetDeviceQueue2;
@@ -225,6 +229,39 @@
     PFN_vkGetBufferOpaqueCaptureAddress GetBufferOpaqueCaptureAddress;
     PFN_vkGetBufferDeviceAddress GetBufferDeviceAddress;
     PFN_vkGetDeviceMemoryOpaqueCaptureAddress GetDeviceMemoryOpaqueCaptureAddress;
+    PFN_vkCmdSetCullMode CmdSetCullMode;
+    PFN_vkCmdSetFrontFace CmdSetFrontFace;
+    PFN_vkCmdSetPrimitiveTopology CmdSetPrimitiveTopology;
+    PFN_vkCmdSetViewportWithCount CmdSetViewportWithCount;
+    PFN_vkCmdSetScissorWithCount CmdSetScissorWithCount;
+    PFN_vkCmdBindVertexBuffers2 CmdBindVertexBuffers2;
+    PFN_vkCmdSetDepthTestEnable CmdSetDepthTestEnable;
+    PFN_vkCmdSetDepthWriteEnable CmdSetDepthWriteEnable;
+    PFN_vkCmdSetDepthCompareOp CmdSetDepthCompareOp;
+    PFN_vkCmdSetDepthBoundsTestEnable CmdSetDepthBoundsTestEnable;
+    PFN_vkCmdSetStencilTestEnable CmdSetStencilTestEnable;
+    PFN_vkCmdSetStencilOp CmdSetStencilOp;
+    PFN_vkCmdSetRasterizerDiscardEnable CmdSetRasterizerDiscardEnable;
+    PFN_vkCmdSetDepthBiasEnable CmdSetDepthBiasEnable;
+    PFN_vkCmdSetPrimitiveRestartEnable CmdSetPrimitiveRestartEnable;
+    PFN_vkCreatePrivateDataSlot CreatePrivateDataSlot;
+    PFN_vkDestroyPrivateDataSlot DestroyPrivateDataSlot;
+    PFN_vkSetPrivateData SetPrivateData;
+    PFN_vkGetPrivateData GetPrivateData;
+    PFN_vkCmdCopyBuffer2 CmdCopyBuffer2;
+    PFN_vkCmdCopyImage2 CmdCopyImage2;
+    PFN_vkCmdBlitImage2 CmdBlitImage2;
+    PFN_vkCmdCopyBufferToImage2 CmdCopyBufferToImage2;
+    PFN_vkCmdCopyImageToBuffer2 CmdCopyImageToBuffer2;
+    PFN_vkCmdResolveImage2 CmdResolveImage2;
+    PFN_vkCmdSetEvent2 CmdSetEvent2;
+    PFN_vkCmdResetEvent2 CmdResetEvent2;
+    PFN_vkCmdWaitEvents2 CmdWaitEvents2;
+    PFN_vkCmdPipelineBarrier2 CmdPipelineBarrier2;
+    PFN_vkQueueSubmit2 QueueSubmit2;
+    PFN_vkCmdWriteTimestamp2 CmdWriteTimestamp2;
+    PFN_vkCmdBeginRendering CmdBeginRendering;
+    PFN_vkCmdEndRendering CmdEndRendering;
     // clang-format on
 };
 
diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp
index cf774fd..9225062 100644
--- a/vulkan/libvulkan/driver.cpp
+++ b/vulkan/libvulkan/driver.cpp
@@ -365,7 +365,7 @@
                                      const VkAllocationCallbacks& allocator)
     : is_instance_(true),
       allocator_(allocator),
-      loader_api_version_(VK_API_VERSION_1_1),
+      loader_api_version_(VK_API_VERSION_1_3),
       icd_api_version_(icd_api_version),
       physical_dev_(VK_NULL_HANDLE),
       instance_info_(create_info),
@@ -377,7 +377,7 @@
                                      const VkAllocationCallbacks& allocator)
     : is_instance_(false),
       allocator_(allocator),
-      loader_api_version_(VK_API_VERSION_1_1),
+      loader_api_version_(VK_API_VERSION_1_3),
       icd_api_version_(icd_api_version),
       physical_dev_(physical_dev),
       dev_info_(create_info),
@@ -519,6 +519,14 @@
         is_instance_ ? loader_api_version_
                      : std::min(icd_api_version_, loader_api_version_);
     switch (api_version) {
+        case VK_API_VERSION_1_3:
+            hook_extensions_.set(ProcHook::EXTENSION_CORE_1_3);
+            hal_extensions_.set(ProcHook::EXTENSION_CORE_1_3);
+            [[clang::fallthrough]];
+        case VK_API_VERSION_1_2:
+            hook_extensions_.set(ProcHook::EXTENSION_CORE_1_2);
+            hal_extensions_.set(ProcHook::EXTENSION_CORE_1_2);
+            [[clang::fallthrough]];
         case VK_API_VERSION_1_1:
             hook_extensions_.set(ProcHook::EXTENSION_CORE_1_1);
             hal_extensions_.set(ProcHook::EXTENSION_CORE_1_1);
@@ -653,6 +661,7 @@
             case ProcHook::EXTENSION_CORE_1_0:
             case ProcHook::EXTENSION_CORE_1_1:
             case ProcHook::EXTENSION_CORE_1_2:
+            case ProcHook::EXTENSION_CORE_1_3:
             case ProcHook::EXTENSION_COUNT:
                 // Device and meta extensions. If we ever get here it's a bug in
                 // our code. But enumerating them lets us avoid having a default
@@ -707,6 +716,7 @@
             case ProcHook::EXTENSION_CORE_1_0:
             case ProcHook::EXTENSION_CORE_1_1:
             case ProcHook::EXTENSION_CORE_1_2:
+            case ProcHook::EXTENSION_CORE_1_3:
             case ProcHook::EXTENSION_COUNT:
                 // Instance and meta extensions. If we ever get here it's a bug
                 // in our code. But enumerating them lets us avoid having a
@@ -1111,7 +1121,7 @@
         if (result != VK_SUCCESS)
             return result;
 
-        icd_api_version ^= VK_VERSION_PATCH(icd_api_version);
+        icd_api_version ^= VK_API_VERSION_PATCH(icd_api_version);
     }
 
     CreateInfoWrapper wrapper(*pCreateInfo, icd_api_version, data_allocator);
@@ -1195,7 +1205,7 @@
 
     CreateInfoWrapper wrapper(
         physicalDevice, *pCreateInfo,
-        properties.apiVersion ^ VK_VERSION_PATCH(properties.apiVersion),
+        properties.apiVersion ^ VK_API_VERSION_PATCH(properties.apiVersion),
         data_allocator);
     VkResult result = wrapper.Validate();
     if (result != VK_SUCCESS)
diff --git a/vulkan/libvulkan/driver_gen.h b/vulkan/libvulkan/driver_gen.h
index 047e774..819f6b2 100644
--- a/vulkan/libvulkan/driver_gen.h
+++ b/vulkan/libvulkan/driver_gen.h
@@ -58,6 +58,7 @@
         EXTENSION_CORE_1_0,
         EXTENSION_CORE_1_1,
         EXTENSION_CORE_1_2,
+        EXTENSION_CORE_1_3,
         EXTENSION_COUNT,
         EXTENSION_UNKNOWN,
     };
diff --git a/vulkan/libvulkan/libvulkan.map.txt b/vulkan/libvulkan/libvulkan.map.txt
index df97d7f..f49e8f3 100644
--- a/vulkan/libvulkan/libvulkan.map.txt
+++ b/vulkan/libvulkan/libvulkan.map.txt
@@ -11,20 +11,27 @@
     vkBindImageMemory;
     vkBindImageMemory2; # introduced=28
     vkCmdBeginQuery;
+    vkCmdBeginRendering; # introduced=33
     vkCmdBeginRenderPass;
     vkCmdBeginRenderPass2; # introduced=31
     vkCmdBindDescriptorSets;
     vkCmdBindIndexBuffer;
     vkCmdBindPipeline;
     vkCmdBindVertexBuffers;
+    vkCmdBindVertexBuffers2; #introduced=33
     vkCmdBlitImage;
+    vkCmdBlitImage2; #introduced=33
     vkCmdClearAttachments;
     vkCmdClearColorImage;
     vkCmdClearDepthStencilImage;
     vkCmdCopyBuffer;
+    vkCmdCopyBuffer2; #introduced=33
     vkCmdCopyBufferToImage;
+    vkCmdCopyBufferToImage2; #introduced=33
     vkCmdCopyImage;
+    vkCmdCopyImage2; #introduced=33
     vkCmdCopyImageToBuffer;
+    vkCmdCopyImageToBuffer2; #introduced=33
     vkCmdCopyQueryPoolResults;
     vkCmdDispatch;
     vkCmdDispatchBase; # introduced=28
@@ -36,6 +43,7 @@
     vkCmdDrawIndirect;
     vkCmdDrawIndirectCount; # introduced=31
     vkCmdEndQuery;
+    vkCmdEndRendering; #introduced=33
     vkCmdEndRenderPass;
     vkCmdEndRenderPass2; # introduced=31
     vkCmdExecuteCommands;
@@ -43,24 +51,44 @@
     vkCmdNextSubpass;
     vkCmdNextSubpass2; # introduced=31
     vkCmdPipelineBarrier;
+    vkCmdPipelineBarrier2; #introduced=33
     vkCmdPushConstants;
     vkCmdResetEvent;
+    vkCmdResetEvent2; #introduced=33
     vkCmdResetQueryPool;
     vkCmdResolveImage;
+    vkCmdResolveImage2; #introduced=33
     vkCmdSetBlendConstants;
+    vkCmdSetCullMode; #introduced=33
     vkCmdSetDepthBias;
+    vkCmdSetDepthBiasEnable; #introduced=33
     vkCmdSetDepthBounds;
+    vkCmdSetDepthBoundsTestEnable; #introduced=33
+    vkCmdSetDepthCompareOp; #introduced=33
+    vkCmdSetDepthTestEnable; #introduced=33
+    vkCmdSetDepthWriteEnable; #introduced=33
     vkCmdSetDeviceMask; # introduced=28
     vkCmdSetEvent;
+    vkCmdSetEvent2; #introduced=33
+    vkCmdSetFrontFace; #introduced=33
     vkCmdSetLineWidth;
+    vkCmdSetPrimitiveRestartEnable; #introduced=33
+    vkCmdSetPrimitiveTopology; #introduced=33
+    vkCmdSetRasterizerDiscardEnable; #introduced=33
     vkCmdSetScissor;
+    vkCmdSetScissorWithCount; #introduced=33
     vkCmdSetStencilCompareMask;
+    vkCmdSetStencilOp; #introduced=33
     vkCmdSetStencilReference;
+    vkCmdSetStencilTestEnable; #introduced=33
     vkCmdSetStencilWriteMask;
     vkCmdSetViewport;
+    vkCmdSetViewportWithCount; #introduced=33
     vkCmdUpdateBuffer;
     vkCmdWaitEvents;
+    vkCmdWaitEvents2; #introduced=33
     vkCmdWriteTimestamp;
+    vkCmdWriteTimestamp2; #introduced=33
     vkCreateAndroidSurfaceKHR;
     vkCreateBuffer;
     vkCreateBufferView;
@@ -79,6 +107,7 @@
     vkCreateInstance;
     vkCreatePipelineCache;
     vkCreatePipelineLayout;
+    vkCreatePrivateDataSlot; #introduced=33
     vkCreateQueryPool;
     vkCreateRenderPass;
     vkCreateRenderPass2; # introduced=31
@@ -103,6 +132,7 @@
     vkDestroyPipeline;
     vkDestroyPipelineCache;
     vkDestroyPipelineLayout;
+    vkDestroyPrivateDataSlot; #introduced=33
     vkDestroyQueryPool;
     vkDestroyRenderPass;
     vkDestroySampler;
@@ -130,9 +160,12 @@
     vkGetBufferMemoryRequirements2; # introduced=28
     vkGetBufferOpaqueCaptureAddress; # introduced=31
     vkGetDescriptorSetLayoutSupport; # introduced=28
+    vkGetDeviceBufferMemoryRequirements; #introduced=33
     vkGetDeviceGroupPeerMemoryFeatures; # introduced=28
     vkGetDeviceGroupPresentCapabilitiesKHR; # introduced=28
     vkGetDeviceGroupSurfacePresentModesKHR; # introduced=28
+    vkGetDeviceImageMemoryRequirements; #introduced=33
+    vkGetDeviceImageSparseMemoryRequirements; #introduced=33
     vkGetDeviceMemoryCommitment;
     vkGetDeviceMemoryOpaqueCaptureAddress; # introduced=31
     vkGetDeviceProcAddr;
@@ -169,7 +202,9 @@
     vkGetPhysicalDeviceSurfaceFormatsKHR;
     vkGetPhysicalDeviceSurfacePresentModesKHR;
     vkGetPhysicalDeviceSurfaceSupportKHR;
+    vkGetPhysicalDeviceToolProperties; #introduced=33
     vkGetPipelineCacheData;
+    vkGetPrivateData; #introduced=33
     vkGetQueryPoolResults;
     vkGetRenderAreaGranularity;
     vkGetSemaphoreCounterValue; # introduced=31
@@ -180,6 +215,7 @@
     vkQueueBindSparse;
     vkQueuePresentKHR;
     vkQueueSubmit;
+    vkQueueSubmit2; #introduced=33
     vkQueueWaitIdle;
     vkResetCommandBuffer;
     vkResetCommandPool;
@@ -188,6 +224,7 @@
     vkResetFences;
     vkResetQueryPool; # introduced=31
     vkSetEvent;
+    vkSetPrivateData; # introduced=33
     vkSignalSemaphore; # introduced=31
     vkTrimCommandPool; # introduced=28
     vkUnmapMemory;
diff --git a/vulkan/nulldrv/null_driver.cpp b/vulkan/nulldrv/null_driver.cpp
index b94233b..3c91150 100644
--- a/vulkan/nulldrv/null_driver.cpp
+++ b/vulkan/nulldrv/null_driver.cpp
@@ -260,7 +260,7 @@
 
 VKAPI_ATTR
 VkResult EnumerateInstanceVersion(uint32_t* pApiVersion) {
-    *pApiVersion = VK_API_VERSION_1_1;
+    *pApiVersion = VK_API_VERSION_1_3;
     return VK_SUCCESS;
 }
 
@@ -397,8 +397,8 @@
 
 void GetPhysicalDeviceProperties(VkPhysicalDevice,
                                  VkPhysicalDeviceProperties* properties) {
-    properties->apiVersion = VK_MAKE_VERSION(1, 0, VK_HEADER_VERSION);
-    properties->driverVersion = VK_MAKE_VERSION(0, 0, 1);
+    properties->apiVersion = VK_MAKE_API_VERSION(0, 1, 2, VK_HEADER_VERSION);
+    properties->driverVersion = VK_MAKE_API_VERSION(0, 0, 0, 1);
     properties->vendorID = 0;
     properties->deviceID = 0;
     properties->deviceType = VK_PHYSICAL_DEVICE_TYPE_OTHER;
@@ -1625,6 +1625,125 @@
     return 0;
 }
 
+void CmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo) {
+}
+
+void CmdEndRendering(VkCommandBuffer commandBuffer) {
+}
+
+void CmdBindVertexBuffers2(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes, const VkDeviceSize* pStrides) {
+}
+
+void CmdBlitImage2(VkCommandBuffer commandBuffer, const VkBlitImageInfo2* pBlitImageInfo) {
+}
+
+void CmdCopyBuffer2(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2* pCopyBufferInfo) {
+}
+
+void CmdCopyImage2(VkCommandBuffer commandBuffer, const VkCopyImageInfo2* pCopyImageInfo) {
+}
+
+void CmdCopyBufferToImage2(VkCommandBuffer commandBuffer, const VkCopyBufferToImageInfo2* pCopyBufferToImageInfo) {
+}
+
+void CmdCopyImageToBuffer2(VkCommandBuffer commandBuffer, const VkCopyImageToBufferInfo2* pCopyImageToBufferInfo) {
+}
+
+void CmdPipelineBarrier2(VkCommandBuffer commandBuffer, const VkDependencyInfo* pDependencyInfo) {
+}
+
+void CmdResetEvent2(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags2 stageMask) {
+}
+
+void CmdResolveImage2(VkCommandBuffer commandBuffer, const VkResolveImageInfo2* pResolveImageInfo) {
+}
+
+void CmdSetCullMode(VkCommandBuffer commandBuffer, VkCullModeFlags cullMode) {
+}
+
+void CmdSetDepthBiasEnable(VkCommandBuffer commandBuffer, VkBool32 depthBiasEnable) {
+}
+
+void CmdSetDepthBoundsTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthBoundsTestEnable) {
+}
+
+void CmdSetDepthCompareOp(VkCommandBuffer commandBuffer, VkCompareOp depthCompareOp) {
+}
+
+void CmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable) {
+}
+
+void CmdSetDepthWriteEnable(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable) {
+}
+
+void CmdSetEvent2(VkCommandBuffer commandBuffer, VkEvent event, const VkDependencyInfo* pDependencyInfo) {
+}
+
+void CmdSetFrontFace(VkCommandBuffer commandBuffer, VkFrontFace frontFace) {
+}
+
+void CmdSetPrimitiveRestartEnable(VkCommandBuffer commandBuffer, VkBool32 primitiveRestartEnable) {
+}
+
+void CmdSetPrimitiveTopology(VkCommandBuffer commandBuffer, VkPrimitiveTopology primitiveTopology) {
+}
+
+void CmdSetRasterizerDiscardEnable(VkCommandBuffer commandBuffer, VkBool32 rasterizerDiscardEnable) {
+}
+
+void CmdSetScissorWithCount(VkCommandBuffer commandBuffer, uint32_t scissorCount, const VkRect2D* pScissors) {
+}
+
+void CmdSetStencilOp(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, VkStencilOp failOp, VkStencilOp passOp, VkStencilOp depthFailOp, VkCompareOp compareOp) {
+}
+
+void CmdSetStencilTestEnable(VkCommandBuffer commandBuffer, VkBool32 stencilTestEnable) {
+}
+
+void CmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_t viewportCount, const VkViewport* pViewports) {
+}
+
+void CmdWaitEvents2(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents, const VkDependencyInfo* pDependencyInfos) {
+}
+
+void CmdWriteTimestamp2(VkCommandBuffer commandBuffer, VkPipelineStageFlags2 stage, VkQueryPool queryPool, uint32_t query) {
+}
+
+VkResult CreatePrivateDataSlot(VkDevice device, const VkPrivateDataSlotCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPrivateDataSlot* pPrivateDataSlot) {
+    ALOGV("TODO: vk%s", __FUNCTION__);
+    return VK_SUCCESS;
+}
+
+void DestroyPrivateDataSlot(VkDevice device, VkPrivateDataSlot privateDataSlot, const VkAllocationCallbacks* pAllocator) {
+}
+
+void GetDeviceBufferMemoryRequirements(VkDevice device, const VkDeviceBufferMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements) {
+}
+
+void GetDeviceImageMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements) {
+}
+
+void GetDeviceImageSparseMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements) {
+}
+
+VkResult GetPhysicalDeviceToolProperties(VkPhysicalDevice physicalDevice, uint32_t* pToolCount, VkPhysicalDeviceToolProperties* pToolProperties) {
+    ALOGV("TODO: vk%s", __FUNCTION__);
+    return VK_SUCCESS;
+}
+
+void GetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t* pData) {
+}
+
+VkResult QueueSubmit2(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2* pSubmits, VkFence fence) {
+    ALOGV("TODO: vk%s", __FUNCTION__);
+    return VK_SUCCESS;
+}
+
+VkResult SetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t data) {
+    ALOGV("TODO: vk%s", __FUNCTION__);
+    return VK_SUCCESS;
+}
+
 #pragma clang diagnostic pop
 // clang-format on
 
diff --git a/vulkan/nulldrv/null_driver_gen.cpp b/vulkan/nulldrv/null_driver_gen.cpp
index edda12c..f6dcf09 100644
--- a/vulkan/nulldrv/null_driver_gen.cpp
+++ b/vulkan/nulldrv/null_driver_gen.cpp
@@ -68,18 +68,25 @@
     {"vkCmdBeginQuery", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBeginQuery>(CmdBeginQuery))},
     {"vkCmdBeginRenderPass", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBeginRenderPass>(CmdBeginRenderPass))},
     {"vkCmdBeginRenderPass2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBeginRenderPass2>(CmdBeginRenderPass2))},
+    {"vkCmdBeginRendering", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBeginRendering>(CmdBeginRendering))},
     {"vkCmdBindDescriptorSets", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBindDescriptorSets>(CmdBindDescriptorSets))},
     {"vkCmdBindIndexBuffer", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBindIndexBuffer>(CmdBindIndexBuffer))},
     {"vkCmdBindPipeline", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBindPipeline>(CmdBindPipeline))},
     {"vkCmdBindVertexBuffers", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBindVertexBuffers>(CmdBindVertexBuffers))},
+    {"vkCmdBindVertexBuffers2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBindVertexBuffers2>(CmdBindVertexBuffers2))},
     {"vkCmdBlitImage", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBlitImage>(CmdBlitImage))},
+    {"vkCmdBlitImage2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBlitImage2>(CmdBlitImage2))},
     {"vkCmdClearAttachments", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdClearAttachments>(CmdClearAttachments))},
     {"vkCmdClearColorImage", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdClearColorImage>(CmdClearColorImage))},
     {"vkCmdClearDepthStencilImage", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdClearDepthStencilImage>(CmdClearDepthStencilImage))},
     {"vkCmdCopyBuffer", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdCopyBuffer>(CmdCopyBuffer))},
+    {"vkCmdCopyBuffer2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdCopyBuffer2>(CmdCopyBuffer2))},
     {"vkCmdCopyBufferToImage", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdCopyBufferToImage>(CmdCopyBufferToImage))},
+    {"vkCmdCopyBufferToImage2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdCopyBufferToImage2>(CmdCopyBufferToImage2))},
     {"vkCmdCopyImage", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdCopyImage>(CmdCopyImage))},
+    {"vkCmdCopyImage2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdCopyImage2>(CmdCopyImage2))},
     {"vkCmdCopyImageToBuffer", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdCopyImageToBuffer>(CmdCopyImageToBuffer))},
+    {"vkCmdCopyImageToBuffer2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdCopyImageToBuffer2>(CmdCopyImageToBuffer2))},
     {"vkCmdCopyQueryPoolResults", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdCopyQueryPoolResults>(CmdCopyQueryPoolResults))},
     {"vkCmdDispatch", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdDispatch>(CmdDispatch))},
     {"vkCmdDispatchBase", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdDispatchBase>(CmdDispatchBase))},
@@ -93,29 +100,50 @@
     {"vkCmdEndQuery", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdEndQuery>(CmdEndQuery))},
     {"vkCmdEndRenderPass", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdEndRenderPass>(CmdEndRenderPass))},
     {"vkCmdEndRenderPass2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdEndRenderPass2>(CmdEndRenderPass2))},
+    {"vkCmdEndRendering", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdEndRendering>(CmdEndRendering))},
     {"vkCmdExecuteCommands", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdExecuteCommands>(CmdExecuteCommands))},
     {"vkCmdFillBuffer", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdFillBuffer>(CmdFillBuffer))},
     {"vkCmdNextSubpass", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdNextSubpass>(CmdNextSubpass))},
     {"vkCmdNextSubpass2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdNextSubpass2>(CmdNextSubpass2))},
     {"vkCmdPipelineBarrier", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdPipelineBarrier>(CmdPipelineBarrier))},
+    {"vkCmdPipelineBarrier2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdPipelineBarrier2>(CmdPipelineBarrier2))},
     {"vkCmdPushConstants", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdPushConstants>(CmdPushConstants))},
     {"vkCmdResetEvent", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdResetEvent>(CmdResetEvent))},
+    {"vkCmdResetEvent2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdResetEvent2>(CmdResetEvent2))},
     {"vkCmdResetQueryPool", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdResetQueryPool>(CmdResetQueryPool))},
     {"vkCmdResolveImage", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdResolveImage>(CmdResolveImage))},
+    {"vkCmdResolveImage2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdResolveImage2>(CmdResolveImage2))},
     {"vkCmdSetBlendConstants", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetBlendConstants>(CmdSetBlendConstants))},
+    {"vkCmdSetCullMode", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetCullMode>(CmdSetCullMode))},
     {"vkCmdSetDepthBias", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetDepthBias>(CmdSetDepthBias))},
+    {"vkCmdSetDepthBiasEnable", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetDepthBiasEnable>(CmdSetDepthBiasEnable))},
     {"vkCmdSetDepthBounds", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetDepthBounds>(CmdSetDepthBounds))},
+    {"vkCmdSetDepthBoundsTestEnable", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetDepthBoundsTestEnable>(CmdSetDepthBoundsTestEnable))},
+    {"vkCmdSetDepthCompareOp", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetDepthCompareOp>(CmdSetDepthCompareOp))},
+    {"vkCmdSetDepthTestEnable", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetDepthTestEnable>(CmdSetDepthTestEnable))},
+    {"vkCmdSetDepthWriteEnable", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetDepthWriteEnable>(CmdSetDepthWriteEnable))},
     {"vkCmdSetDeviceMask", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetDeviceMask>(CmdSetDeviceMask))},
     {"vkCmdSetEvent", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetEvent>(CmdSetEvent))},
+    {"vkCmdSetEvent2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetEvent2>(CmdSetEvent2))},
+    {"vkCmdSetFrontFace", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetFrontFace>(CmdSetFrontFace))},
     {"vkCmdSetLineWidth", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetLineWidth>(CmdSetLineWidth))},
+    {"vkCmdSetPrimitiveRestartEnable", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetPrimitiveRestartEnable>(CmdSetPrimitiveRestartEnable))},
+    {"vkCmdSetPrimitiveTopology", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetPrimitiveTopology>(CmdSetPrimitiveTopology))},
+    {"vkCmdSetRasterizerDiscardEnable", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetRasterizerDiscardEnable>(CmdSetRasterizerDiscardEnable))},
     {"vkCmdSetScissor", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetScissor>(CmdSetScissor))},
+    {"vkCmdSetScissorWithCount", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetScissorWithCount>(CmdSetScissorWithCount))},
     {"vkCmdSetStencilCompareMask", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetStencilCompareMask>(CmdSetStencilCompareMask))},
+    {"vkCmdSetStencilOp", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetStencilOp>(CmdSetStencilOp))},
     {"vkCmdSetStencilReference", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetStencilReference>(CmdSetStencilReference))},
+    {"vkCmdSetStencilTestEnable", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetStencilTestEnable>(CmdSetStencilTestEnable))},
     {"vkCmdSetStencilWriteMask", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetStencilWriteMask>(CmdSetStencilWriteMask))},
     {"vkCmdSetViewport", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetViewport>(CmdSetViewport))},
+    {"vkCmdSetViewportWithCount", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetViewportWithCount>(CmdSetViewportWithCount))},
     {"vkCmdUpdateBuffer", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdUpdateBuffer>(CmdUpdateBuffer))},
     {"vkCmdWaitEvents", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdWaitEvents>(CmdWaitEvents))},
+    {"vkCmdWaitEvents2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdWaitEvents2>(CmdWaitEvents2))},
     {"vkCmdWriteTimestamp", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdWriteTimestamp>(CmdWriteTimestamp))},
+    {"vkCmdWriteTimestamp2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdWriteTimestamp2>(CmdWriteTimestamp2))},
     {"vkCreateBuffer", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreateBuffer>(CreateBuffer))},
     {"vkCreateBufferView", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreateBufferView>(CreateBufferView))},
     {"vkCreateCommandPool", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreateCommandPool>(CreateCommandPool))},
@@ -134,6 +162,7 @@
     {"vkCreateInstance", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreateInstance>(CreateInstance))},
     {"vkCreatePipelineCache", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreatePipelineCache>(CreatePipelineCache))},
     {"vkCreatePipelineLayout", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreatePipelineLayout>(CreatePipelineLayout))},
+    {"vkCreatePrivateDataSlot", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreatePrivateDataSlot>(CreatePrivateDataSlot))},
     {"vkCreateQueryPool", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreateQueryPool>(CreateQueryPool))},
     {"vkCreateRenderPass", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreateRenderPass>(CreateRenderPass))},
     {"vkCreateRenderPass2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreateRenderPass2>(CreateRenderPass2))},
@@ -159,6 +188,7 @@
     {"vkDestroyPipeline", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkDestroyPipeline>(DestroyPipeline))},
     {"vkDestroyPipelineCache", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkDestroyPipelineCache>(DestroyPipelineCache))},
     {"vkDestroyPipelineLayout", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkDestroyPipelineLayout>(DestroyPipelineLayout))},
+    {"vkDestroyPrivateDataSlot", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkDestroyPrivateDataSlot>(DestroyPrivateDataSlot))},
     {"vkDestroyQueryPool", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkDestroyQueryPool>(DestroyQueryPool))},
     {"vkDestroyRenderPass", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkDestroyRenderPass>(DestroyRenderPass))},
     {"vkDestroySampler", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkDestroySampler>(DestroySampler))},
@@ -183,7 +213,10 @@
     {"vkGetBufferMemoryRequirements2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetBufferMemoryRequirements2>(GetBufferMemoryRequirements2))},
     {"vkGetBufferOpaqueCaptureAddress", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetBufferOpaqueCaptureAddress>(GetBufferOpaqueCaptureAddress))},
     {"vkGetDescriptorSetLayoutSupport", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDescriptorSetLayoutSupport>(GetDescriptorSetLayoutSupport))},
+    {"vkGetDeviceBufferMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceBufferMemoryRequirements>(GetDeviceBufferMemoryRequirements))},
     {"vkGetDeviceGroupPeerMemoryFeatures", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceGroupPeerMemoryFeatures>(GetDeviceGroupPeerMemoryFeatures))},
+    {"vkGetDeviceImageMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceImageMemoryRequirements>(GetDeviceImageMemoryRequirements))},
+    {"vkGetDeviceImageSparseMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceImageSparseMemoryRequirements>(GetDeviceImageSparseMemoryRequirements))},
     {"vkGetDeviceMemoryCommitment", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceMemoryCommitment>(GetDeviceMemoryCommitment))},
     {"vkGetDeviceMemoryOpaqueCaptureAddress", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceMemoryOpaqueCaptureAddress>(GetDeviceMemoryOpaqueCaptureAddress))},
     {"vkGetDeviceProcAddr", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceProcAddr>(GetDeviceProcAddr))},
@@ -221,7 +254,9 @@
     {"vkGetPhysicalDeviceSparseImageFormatProperties", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetPhysicalDeviceSparseImageFormatProperties>(GetPhysicalDeviceSparseImageFormatProperties))},
     {"vkGetPhysicalDeviceSparseImageFormatProperties2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetPhysicalDeviceSparseImageFormatProperties2>(GetPhysicalDeviceSparseImageFormatProperties2))},
     {"vkGetPhysicalDeviceSparseImageFormatProperties2KHR", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetPhysicalDeviceSparseImageFormatProperties2KHR>(GetPhysicalDeviceSparseImageFormatProperties2KHR))},
+    {"vkGetPhysicalDeviceToolProperties", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetPhysicalDeviceToolProperties>(GetPhysicalDeviceToolProperties))},
     {"vkGetPipelineCacheData", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetPipelineCacheData>(GetPipelineCacheData))},
+    {"vkGetPrivateData", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetPrivateData>(GetPrivateData))},
     {"vkGetQueryPoolResults", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetQueryPoolResults>(GetQueryPoolResults))},
     {"vkGetRenderAreaGranularity", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetRenderAreaGranularity>(GetRenderAreaGranularity))},
     {"vkGetSemaphoreCounterValue", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetSemaphoreCounterValue>(GetSemaphoreCounterValue))},
@@ -233,6 +268,7 @@
     {"vkQueueBindSparse", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkQueueBindSparse>(QueueBindSparse))},
     {"vkQueueSignalReleaseImageANDROID", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkQueueSignalReleaseImageANDROID>(QueueSignalReleaseImageANDROID))},
     {"vkQueueSubmit", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkQueueSubmit>(QueueSubmit))},
+    {"vkQueueSubmit2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkQueueSubmit2>(QueueSubmit2))},
     {"vkQueueWaitIdle", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkQueueWaitIdle>(QueueWaitIdle))},
     {"vkResetCommandBuffer", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkResetCommandBuffer>(ResetCommandBuffer))},
     {"vkResetCommandPool", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkResetCommandPool>(ResetCommandPool))},
@@ -241,6 +277,7 @@
     {"vkResetFences", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkResetFences>(ResetFences))},
     {"vkResetQueryPool", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkResetQueryPool>(ResetQueryPool))},
     {"vkSetEvent", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkSetEvent>(SetEvent))},
+    {"vkSetPrivateData", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkSetPrivateData>(SetPrivateData))},
     {"vkSignalSemaphore", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkSignalSemaphore>(SignalSemaphore))},
     {"vkTrimCommandPool", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkTrimCommandPool>(TrimCommandPool))},
     {"vkUnmapMemory", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkUnmapMemory>(UnmapMemory))},
diff --git a/vulkan/nulldrv/null_driver_gen.h b/vulkan/nulldrv/null_driver_gen.h
index e59cae9..3e003e3 100644
--- a/vulkan/nulldrv/null_driver_gen.h
+++ b/vulkan/nulldrv/null_driver_gen.h
@@ -200,6 +200,9 @@
 VKAPI_ATTR void GetBufferMemoryRequirements2(VkDevice device, const VkBufferMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
 VKAPI_ATTR void GetImageMemoryRequirements2(VkDevice device, const VkImageMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
 VKAPI_ATTR void GetImageSparseMemoryRequirements2(VkDevice device, const VkImageSparseMemoryRequirementsInfo2* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements);
+VKAPI_ATTR void GetDeviceBufferMemoryRequirements(VkDevice device, const VkDeviceBufferMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements);
+VKAPI_ATTR void GetDeviceImageMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements);
+VKAPI_ATTR void GetDeviceImageSparseMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements);
 VKAPI_ATTR VkResult CreateSamplerYcbcrConversion(VkDevice device, const VkSamplerYcbcrConversionCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSamplerYcbcrConversion* pYcbcrConversion);
 VKAPI_ATTR void DestroySamplerYcbcrConversion(VkDevice device, VkSamplerYcbcrConversion ycbcrConversion, const VkAllocationCallbacks* pAllocator);
 VKAPI_ATTR void GetDeviceQueue2(VkDevice device, const VkDeviceQueueInfo2* pQueueInfo, VkQueue* pQueue);
@@ -220,6 +223,40 @@
 VKAPI_ATTR uint64_t GetBufferOpaqueCaptureAddress(VkDevice device, const VkBufferDeviceAddressInfo* pInfo);
 VKAPI_ATTR VkDeviceAddress GetBufferDeviceAddress(VkDevice device, const VkBufferDeviceAddressInfo* pInfo);
 VKAPI_ATTR uint64_t GetDeviceMemoryOpaqueCaptureAddress(VkDevice device, const VkDeviceMemoryOpaqueCaptureAddressInfo* pInfo);
+VKAPI_ATTR VkResult GetPhysicalDeviceToolProperties(VkPhysicalDevice physicalDevice, uint32_t* pToolCount, VkPhysicalDeviceToolProperties* pToolProperties);
+VKAPI_ATTR void CmdSetCullMode(VkCommandBuffer commandBuffer, VkCullModeFlags cullMode);
+VKAPI_ATTR void CmdSetFrontFace(VkCommandBuffer commandBuffer, VkFrontFace frontFace);
+VKAPI_ATTR void CmdSetPrimitiveTopology(VkCommandBuffer commandBuffer, VkPrimitiveTopology primitiveTopology);
+VKAPI_ATTR void CmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_t viewportCount, const VkViewport* pViewports);
+VKAPI_ATTR void CmdSetScissorWithCount(VkCommandBuffer commandBuffer, uint32_t scissorCount, const VkRect2D* pScissors);
+VKAPI_ATTR void CmdBindVertexBuffers2(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes, const VkDeviceSize* pStrides);
+VKAPI_ATTR void CmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable);
+VKAPI_ATTR void CmdSetDepthWriteEnable(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable);
+VKAPI_ATTR void CmdSetDepthCompareOp(VkCommandBuffer commandBuffer, VkCompareOp depthCompareOp);
+VKAPI_ATTR void CmdSetDepthBoundsTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthBoundsTestEnable);
+VKAPI_ATTR void CmdSetStencilTestEnable(VkCommandBuffer commandBuffer, VkBool32 stencilTestEnable);
+VKAPI_ATTR void CmdSetStencilOp(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, VkStencilOp failOp, VkStencilOp passOp, VkStencilOp depthFailOp, VkCompareOp compareOp);
+VKAPI_ATTR void CmdSetRasterizerDiscardEnable(VkCommandBuffer commandBuffer, VkBool32 rasterizerDiscardEnable);
+VKAPI_ATTR void CmdSetDepthBiasEnable(VkCommandBuffer commandBuffer, VkBool32 depthBiasEnable);
+VKAPI_ATTR void CmdSetPrimitiveRestartEnable(VkCommandBuffer commandBuffer, VkBool32 primitiveRestartEnable);
+VKAPI_ATTR VkResult CreatePrivateDataSlot(VkDevice device, const VkPrivateDataSlotCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPrivateDataSlot* pPrivateDataSlot);
+VKAPI_ATTR void DestroyPrivateDataSlot(VkDevice device, VkPrivateDataSlot privateDataSlot, const VkAllocationCallbacks* pAllocator);
+VKAPI_ATTR VkResult SetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t data);
+VKAPI_ATTR void GetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t* pData);
+VKAPI_ATTR void CmdCopyBuffer2(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2* pCopyBufferInfo);
+VKAPI_ATTR void CmdCopyImage2(VkCommandBuffer commandBuffer, const VkCopyImageInfo2* pCopyImageInfo);
+VKAPI_ATTR void CmdBlitImage2(VkCommandBuffer commandBuffer, const VkBlitImageInfo2* pBlitImageInfo);
+VKAPI_ATTR void CmdCopyBufferToImage2(VkCommandBuffer commandBuffer, const VkCopyBufferToImageInfo2* pCopyBufferToImageInfo);
+VKAPI_ATTR void CmdCopyImageToBuffer2(VkCommandBuffer commandBuffer, const VkCopyImageToBufferInfo2* pCopyImageToBufferInfo);
+VKAPI_ATTR void CmdResolveImage2(VkCommandBuffer commandBuffer, const VkResolveImageInfo2* pResolveImageInfo);
+VKAPI_ATTR void CmdSetEvent2(VkCommandBuffer commandBuffer, VkEvent event, const VkDependencyInfo* pDependencyInfo);
+VKAPI_ATTR void CmdResetEvent2(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags2 stageMask);
+VKAPI_ATTR void CmdWaitEvents2(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents, const VkDependencyInfo* pDependencyInfos);
+VKAPI_ATTR void CmdPipelineBarrier2(VkCommandBuffer commandBuffer, const VkDependencyInfo* pDependencyInfo);
+VKAPI_ATTR VkResult QueueSubmit2(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2* pSubmits, VkFence fence);
+VKAPI_ATTR void CmdWriteTimestamp2(VkCommandBuffer commandBuffer, VkPipelineStageFlags2 stage, VkQueryPool queryPool, uint32_t query);
+VKAPI_ATTR void CmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo);
+VKAPI_ATTR void CmdEndRendering(VkCommandBuffer commandBuffer);
 // clang-format on
 
 }  // namespace null_driver
diff --git a/vulkan/vkjson/vkjson.cc b/vulkan/vkjson/vkjson.cc
index 438e5dd..da6b00a 100644
--- a/vulkan/vkjson/vkjson.cc
+++ b/vulkan/vkjson/vkjson.cc
@@ -387,6 +387,19 @@
   }
 };
 
+template <>
+struct EnumTraits<VkShaderFloatControlsIndependence> {
+  static bool exist(uint32_t e) {
+    switch (e) {
+      case VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_32_BIT_ONLY:
+      case VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_ALL:
+      case VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_NONE:
+        return true;
+    }
+    return false;
+  }
+};
+
 // VkSparseImageFormatProperties
 
 template <typename Visitor>
@@ -407,6 +420,7 @@
     visitor->Visit("maxResourceSize", &properties->maxResourceSize);
 }
 
+// clang-format off
 template <typename Visitor>
 inline bool Iterate(Visitor* visitor, VkPhysicalDeviceLimits* limits) {
   return
@@ -605,6 +619,200 @@
 }
 
 template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkJsonCore12* core) {
+  return
+    visitor->Visit("features", &core->features) &&
+    visitor->Visit("properties", &core->properties);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkPhysicalDeviceVulkan12Properties* properties) {
+  return
+    visitor->Visit("driverID", &properties->driverID) &&
+    visitor->Visit("driverName", &properties->driverName) &&
+    visitor->Visit("driverInfo", &properties->driverInfo) &&
+    visitor->Visit("conformanceVersion", &properties->conformanceVersion) &&
+    visitor->Visit("denormBehaviorIndependence", &properties->denormBehaviorIndependence) &&
+    visitor->Visit("roundingModeIndependence", &properties->roundingModeIndependence) &&
+    visitor->Visit("shaderSignedZeroInfNanPreserveFloat16", &properties->shaderSignedZeroInfNanPreserveFloat16) &&
+    visitor->Visit("shaderSignedZeroInfNanPreserveFloat32", &properties->shaderSignedZeroInfNanPreserveFloat32) &&
+    visitor->Visit("shaderSignedZeroInfNanPreserveFloat64", &properties->shaderSignedZeroInfNanPreserveFloat64) &&
+    visitor->Visit("shaderDenormPreserveFloat16", &properties->shaderDenormPreserveFloat16) &&
+    visitor->Visit("shaderDenormPreserveFloat32", &properties->shaderDenormPreserveFloat32) &&
+    visitor->Visit("shaderDenormPreserveFloat64", &properties->shaderDenormPreserveFloat64) &&
+    visitor->Visit("shaderDenormFlushToZeroFloat16", &properties->shaderDenormFlushToZeroFloat16) &&
+    visitor->Visit("shaderDenormFlushToZeroFloat32", &properties->shaderDenormFlushToZeroFloat32) &&
+    visitor->Visit("shaderDenormFlushToZeroFloat64", &properties->shaderDenormFlushToZeroFloat64) &&
+    visitor->Visit("shaderRoundingModeRTEFloat16", &properties->shaderRoundingModeRTEFloat16) &&
+    visitor->Visit("shaderRoundingModeRTEFloat32", &properties->shaderRoundingModeRTEFloat32) &&
+    visitor->Visit("shaderRoundingModeRTEFloat64", &properties->shaderRoundingModeRTEFloat64) &&
+    visitor->Visit("shaderRoundingModeRTZFloat16", &properties->shaderRoundingModeRTZFloat16) &&
+    visitor->Visit("shaderRoundingModeRTZFloat32", &properties->shaderRoundingModeRTZFloat32) &&
+    visitor->Visit("shaderRoundingModeRTZFloat64", &properties->shaderRoundingModeRTZFloat64) &&
+    visitor->Visit("maxUpdateAfterBindDescriptorsInAllPools", &properties->maxUpdateAfterBindDescriptorsInAllPools) &&
+    visitor->Visit("shaderUniformBufferArrayNonUniformIndexingNative", &properties->shaderUniformBufferArrayNonUniformIndexingNative) &&
+    visitor->Visit("shaderSampledImageArrayNonUniformIndexingNative", &properties->shaderSampledImageArrayNonUniformIndexingNative) &&
+    visitor->Visit("shaderStorageBufferArrayNonUniformIndexingNative", &properties->shaderStorageBufferArrayNonUniformIndexingNative) &&
+    visitor->Visit("shaderStorageImageArrayNonUniformIndexingNative", &properties->shaderStorageImageArrayNonUniformIndexingNative) &&
+    visitor->Visit("shaderInputAttachmentArrayNonUniformIndexingNative", &properties->shaderInputAttachmentArrayNonUniformIndexingNative) &&
+    visitor->Visit("robustBufferAccessUpdateAfterBind", &properties->robustBufferAccessUpdateAfterBind) &&
+    visitor->Visit("quadDivergentImplicitLod", &properties->quadDivergentImplicitLod) &&
+    visitor->Visit("maxPerStageDescriptorUpdateAfterBindSamplers", &properties->maxPerStageDescriptorUpdateAfterBindSamplers) &&
+    visitor->Visit("maxPerStageDescriptorUpdateAfterBindUniformBuffers", &properties->maxPerStageDescriptorUpdateAfterBindUniformBuffers) &&
+    visitor->Visit("maxPerStageDescriptorUpdateAfterBindStorageBuffers", &properties->maxPerStageDescriptorUpdateAfterBindStorageBuffers) &&
+    visitor->Visit("maxPerStageDescriptorUpdateAfterBindSampledImages", &properties->maxPerStageDescriptorUpdateAfterBindSampledImages) &&
+    visitor->Visit("maxPerStageDescriptorUpdateAfterBindStorageImages", &properties->maxPerStageDescriptorUpdateAfterBindStorageImages) &&
+    visitor->Visit("maxPerStageDescriptorUpdateAfterBindInputAttachments", &properties->maxPerStageDescriptorUpdateAfterBindInputAttachments) &&
+    visitor->Visit("maxPerStageUpdateAfterBindResources", &properties->maxPerStageUpdateAfterBindResources) &&
+    visitor->Visit("maxDescriptorSetUpdateAfterBindSamplers", &properties->maxDescriptorSetUpdateAfterBindSamplers) &&
+    visitor->Visit("maxDescriptorSetUpdateAfterBindUniformBuffers", &properties->maxDescriptorSetUpdateAfterBindUniformBuffers) &&
+    visitor->Visit("maxDescriptorSetUpdateAfterBindUniformBuffersDynamic", &properties->maxDescriptorSetUpdateAfterBindUniformBuffersDynamic) &&
+    visitor->Visit("maxDescriptorSetUpdateAfterBindStorageBuffers", &properties->maxDescriptorSetUpdateAfterBindStorageBuffers) &&
+    visitor->Visit("maxDescriptorSetUpdateAfterBindStorageBuffersDynamic", &properties->maxDescriptorSetUpdateAfterBindStorageBuffersDynamic) &&
+    visitor->Visit("maxDescriptorSetUpdateAfterBindSampledImages", &properties->maxDescriptorSetUpdateAfterBindSampledImages) &&
+    visitor->Visit("maxDescriptorSetUpdateAfterBindStorageImages", &properties->maxDescriptorSetUpdateAfterBindStorageImages) &&
+    visitor->Visit("maxDescriptorSetUpdateAfterBindInputAttachments", &properties->maxDescriptorSetUpdateAfterBindInputAttachments) &&
+    visitor->Visit("supportedDepthResolveModes", &properties->supportedDepthResolveModes) &&
+    visitor->Visit("supportedStencilResolveModes", &properties->supportedStencilResolveModes) &&
+    visitor->Visit("independentResolveNone", &properties->independentResolveNone) &&
+    visitor->Visit("independentResolve", &properties->independentResolve) &&
+    visitor->Visit("filterMinmaxSingleComponentFormats", &properties->filterMinmaxSingleComponentFormats) &&
+    visitor->Visit("filterMinmaxImageComponentMapping", &properties->filterMinmaxImageComponentMapping) &&
+    visitor->Visit("maxTimelineSemaphoreValueDifference", &properties->maxTimelineSemaphoreValueDifference) &&
+    visitor->Visit("framebufferIntegerColorSampleCounts", &properties->framebufferIntegerColorSampleCounts);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkPhysicalDeviceVulkan12Features* features) {
+  return
+    visitor->Visit("samplerMirrorClampToEdge", &features->samplerMirrorClampToEdge) &&
+    visitor->Visit("drawIndirectCount", &features->drawIndirectCount) &&
+    visitor->Visit("storageBuffer8BitAccess", &features->storageBuffer8BitAccess) &&
+    visitor->Visit("uniformAndStorageBuffer8BitAccess", &features->uniformAndStorageBuffer8BitAccess) &&
+    visitor->Visit("storagePushConstant8", &features->storagePushConstant8) &&
+    visitor->Visit("shaderBufferInt64Atomics", &features->shaderBufferInt64Atomics) &&
+    visitor->Visit("shaderSharedInt64Atomics", &features->shaderSharedInt64Atomics) &&
+    visitor->Visit("shaderFloat16", &features->shaderFloat16) &&
+    visitor->Visit("shaderInt8", &features->shaderInt8) &&
+    visitor->Visit("descriptorIndexing", &features->descriptorIndexing) &&
+    visitor->Visit("shaderInputAttachmentArrayDynamicIndexing", &features->shaderInputAttachmentArrayDynamicIndexing) &&
+    visitor->Visit("shaderUniformTexelBufferArrayDynamicIndexing", &features->shaderUniformTexelBufferArrayDynamicIndexing) &&
+    visitor->Visit("shaderStorageTexelBufferArrayDynamicIndexing", &features->shaderStorageTexelBufferArrayDynamicIndexing) &&
+    visitor->Visit("shaderUniformBufferArrayNonUniformIndexing", &features->shaderUniformBufferArrayNonUniformIndexing) &&
+    visitor->Visit("shaderSampledImageArrayNonUniformIndexing", &features->shaderSampledImageArrayNonUniformIndexing) &&
+    visitor->Visit("shaderStorageBufferArrayNonUniformIndexing", &features->shaderStorageBufferArrayNonUniformIndexing) &&
+    visitor->Visit("shaderStorageImageArrayNonUniformIndexing", &features->shaderStorageImageArrayNonUniformIndexing) &&
+    visitor->Visit("shaderInputAttachmentArrayNonUniformIndexing", &features->shaderInputAttachmentArrayNonUniformIndexing) &&
+    visitor->Visit("shaderUniformTexelBufferArrayNonUniformIndexing", &features->shaderUniformTexelBufferArrayNonUniformIndexing) &&
+    visitor->Visit("shaderStorageTexelBufferArrayNonUniformIndexing", &features->shaderStorageTexelBufferArrayNonUniformIndexing) &&
+    visitor->Visit("descriptorBindingUniformBufferUpdateAfterBind", &features->descriptorBindingUniformBufferUpdateAfterBind) &&
+    visitor->Visit("descriptorBindingSampledImageUpdateAfterBind", &features->descriptorBindingSampledImageUpdateAfterBind) &&
+    visitor->Visit("descriptorBindingStorageImageUpdateAfterBind", &features->descriptorBindingStorageImageUpdateAfterBind) &&
+    visitor->Visit("descriptorBindingStorageBufferUpdateAfterBind", &features->descriptorBindingStorageBufferUpdateAfterBind) &&
+    visitor->Visit("descriptorBindingUniformTexelBufferUpdateAfterBind", &features->descriptorBindingUniformTexelBufferUpdateAfterBind) &&
+    visitor->Visit("descriptorBindingStorageTexelBufferUpdateAfterBind", &features->descriptorBindingStorageTexelBufferUpdateAfterBind) &&
+    visitor->Visit("descriptorBindingUpdateUnusedWhilePending", &features->descriptorBindingUpdateUnusedWhilePending) &&
+    visitor->Visit("descriptorBindingPartiallyBound", &features->descriptorBindingPartiallyBound) &&
+    visitor->Visit("descriptorBindingVariableDescriptorCount", &features->descriptorBindingVariableDescriptorCount) &&
+    visitor->Visit("runtimeDescriptorArray", &features->runtimeDescriptorArray) &&
+    visitor->Visit("samplerFilterMinmax", &features->samplerFilterMinmax) &&
+    visitor->Visit("scalarBlockLayout", &features->scalarBlockLayout) &&
+    visitor->Visit("imagelessFramebuffer", &features->imagelessFramebuffer) &&
+    visitor->Visit("uniformBufferStandardLayout", &features->uniformBufferStandardLayout) &&
+    visitor->Visit("shaderSubgroupExtendedTypes", &features->shaderSubgroupExtendedTypes) &&
+    visitor->Visit("separateDepthStencilLayouts", &features->separateDepthStencilLayouts) &&
+    visitor->Visit("hostQueryReset", &features->hostQueryReset) &&
+    visitor->Visit("timelineSemaphore", &features->timelineSemaphore) &&
+    visitor->Visit("bufferDeviceAddress", &features->bufferDeviceAddress) &&
+    visitor->Visit("bufferDeviceAddressCaptureReplay", &features->bufferDeviceAddressCaptureReplay) &&
+    visitor->Visit("bufferDeviceAddressMultiDevice", &features->bufferDeviceAddressMultiDevice) &&
+    visitor->Visit("vulkanMemoryModel", &features->vulkanMemoryModel) &&
+    visitor->Visit("vulkanMemoryModelDeviceScope", &features->vulkanMemoryModelDeviceScope) &&
+    visitor->Visit("vulkanMemoryModelAvailabilityVisibilityChains", &features->vulkanMemoryModelAvailabilityVisibilityChains) &&
+    visitor->Visit("shaderOutputViewportIndex", &features->shaderOutputViewportIndex) &&
+    visitor->Visit("shaderOutputLayer", &features->shaderOutputLayer) &&
+    visitor->Visit("shaderOutputLayer", &features->shaderOutputLayer);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkJsonCore13* core) {
+  return
+    visitor->Visit("features", &core->features) &&
+    visitor->Visit("properties", &core->properties);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkPhysicalDeviceVulkan13Properties* properties) {
+  return
+    visitor->Visit("minSubgroupSize", &properties->minSubgroupSize) &&
+    visitor->Visit("maxSubgroupSize", &properties->maxSubgroupSize) &&
+    visitor->Visit("maxComputeWorkgroupSubgroups", &properties->maxComputeWorkgroupSubgroups) &&
+    visitor->Visit("requiredSubgroupSizeStages", &properties->requiredSubgroupSizeStages) &&
+    visitor->Visit("maxInlineUniformBlockSize", &properties->maxInlineUniformBlockSize) &&
+    visitor->Visit("maxPerStageDescriptorInlineUniformBlocks", &properties->maxPerStageDescriptorInlineUniformBlocks) &&
+    visitor->Visit("maxPerStageDescriptorUpdateAfterBindInlineUniformBlocks", &properties->maxPerStageDescriptorUpdateAfterBindInlineUniformBlocks) &&
+    visitor->Visit("maxDescriptorSetInlineUniformBlocks", &properties->maxDescriptorSetInlineUniformBlocks) &&
+    visitor->Visit("maxDescriptorSetUpdateAfterBindInlineUniformBlocks", &properties->maxDescriptorSetUpdateAfterBindInlineUniformBlocks) &&
+    visitor->Visit("maxInlineUniformTotalSize", &properties->maxInlineUniformTotalSize) &&
+    visitor->Visit("integerDotProduct8BitUnsignedAccelerated", &properties->integerDotProduct8BitUnsignedAccelerated) &&
+    visitor->Visit("integerDotProduct8BitSignedAccelerated", &properties->integerDotProduct8BitSignedAccelerated) &&
+    visitor->Visit("integerDotProduct8BitMixedSignednessAccelerated", &properties->integerDotProduct8BitMixedSignednessAccelerated) &&
+    visitor->Visit("integerDotProduct4x8BitPackedUnsignedAccelerated", &properties->integerDotProduct4x8BitPackedUnsignedAccelerated) &&
+    visitor->Visit("integerDotProduct4x8BitPackedSignedAccelerated", &properties->integerDotProduct4x8BitPackedSignedAccelerated) &&
+    visitor->Visit("integerDotProduct4x8BitPackedMixedSignednessAccelerated", &properties->integerDotProduct4x8BitPackedMixedSignednessAccelerated) &&
+    visitor->Visit("integerDotProduct16BitUnsignedAccelerated", &properties->integerDotProduct16BitUnsignedAccelerated) &&
+    visitor->Visit("integerDotProduct16BitSignedAccelerated", &properties->integerDotProduct16BitSignedAccelerated) &&
+    visitor->Visit("integerDotProduct16BitMixedSignednessAccelerated", &properties->integerDotProduct16BitMixedSignednessAccelerated) &&
+    visitor->Visit("integerDotProduct32BitUnsignedAccelerated", &properties->integerDotProduct32BitUnsignedAccelerated) &&
+    visitor->Visit("integerDotProduct32BitSignedAccelerated", &properties->integerDotProduct32BitSignedAccelerated) &&
+    visitor->Visit("integerDotProduct32BitMixedSignednessAccelerated", &properties->integerDotProduct32BitMixedSignednessAccelerated) &&
+    visitor->Visit("integerDotProduct64BitUnsignedAccelerated", &properties->integerDotProduct64BitUnsignedAccelerated) &&
+    visitor->Visit("integerDotProduct64BitSignedAccelerated", &properties->integerDotProduct64BitSignedAccelerated) &&
+    visitor->Visit("integerDotProduct64BitMixedSignednessAccelerated", &properties->integerDotProduct64BitMixedSignednessAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating8BitUnsignedAccelerated", &properties->integerDotProductAccumulatingSaturating8BitUnsignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating8BitSignedAccelerated", &properties->integerDotProductAccumulatingSaturating8BitSignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating8BitMixedSignednessAccelerated", &properties->integerDotProductAccumulatingSaturating8BitMixedSignednessAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating4x8BitPackedUnsignedAccelerated", &properties->integerDotProductAccumulatingSaturating4x8BitPackedUnsignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating4x8BitPackedSignedAccelerated", &properties->integerDotProductAccumulatingSaturating4x8BitPackedSignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating4x8BitPackedMixedSignednessAccelerated", &properties->integerDotProductAccumulatingSaturating4x8BitPackedMixedSignednessAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating16BitUnsignedAccelerated", &properties->integerDotProductAccumulatingSaturating16BitUnsignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating16BitSignedAccelerated", &properties->integerDotProductAccumulatingSaturating16BitSignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating16BitMixedSignednessAccelerated", &properties->integerDotProductAccumulatingSaturating16BitMixedSignednessAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating32BitUnsignedAccelerated", &properties->integerDotProductAccumulatingSaturating32BitUnsignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating32BitSignedAccelerated", &properties->integerDotProductAccumulatingSaturating32BitSignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating32BitMixedSignednessAccelerated", &properties->integerDotProductAccumulatingSaturating32BitMixedSignednessAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating64BitUnsignedAccelerated", &properties->integerDotProductAccumulatingSaturating64BitUnsignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating64BitSignedAccelerated", &properties->integerDotProductAccumulatingSaturating64BitSignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating64BitMixedSignednessAccelerated", &properties->integerDotProductAccumulatingSaturating64BitMixedSignednessAccelerated) &&
+    visitor->Visit("storageTexelBufferOffsetAlignmentBytes", &properties->storageTexelBufferOffsetAlignmentBytes) &&
+    visitor->Visit("storageTexelBufferOffsetSingleTexelAlignment", &properties->storageTexelBufferOffsetSingleTexelAlignment) &&
+    visitor->Visit("uniformTexelBufferOffsetAlignmentBytes", &properties->uniformTexelBufferOffsetAlignmentBytes) &&
+    visitor->Visit("uniformTexelBufferOffsetSingleTexelAlignment", &properties->uniformTexelBufferOffsetSingleTexelAlignment) &&
+    visitor->Visit("maxBufferSize", &properties->maxBufferSize);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkPhysicalDeviceVulkan13Features* features) {
+  return
+    visitor->Visit("robustImageAccess", &features->robustImageAccess) &&
+    visitor->Visit("inlineUniformBlock", &features->inlineUniformBlock) &&
+    visitor->Visit("descriptorBindingInlineUniformBlockUpdateAfterBind", &features->descriptorBindingInlineUniformBlockUpdateAfterBind) &&
+    visitor->Visit("pipelineCreationCacheControl", &features->pipelineCreationCacheControl) &&
+    visitor->Visit("privateData", &features->privateData) &&
+    visitor->Visit("shaderDemoteToHelperInvocation", &features->shaderDemoteToHelperInvocation) &&
+    visitor->Visit("shaderTerminateInvocation", &features->shaderTerminateInvocation) &&
+    visitor->Visit("subgroupSizeControl", &features->subgroupSizeControl) &&
+    visitor->Visit("computeFullSubgroups", &features->computeFullSubgroups) &&
+    visitor->Visit("synchronization2", &features->synchronization2) &&
+    visitor->Visit("textureCompressionASTC_HDR", &features->textureCompressionASTC_HDR) &&
+    visitor->Visit("shaderZeroInitializeWorkgroupMemory", &features->shaderZeroInitializeWorkgroupMemory) &&
+    visitor->Visit("dynamicRendering", &features->dynamicRendering) &&
+    visitor->Visit("shaderIntegerDotProduct", &features->shaderIntegerDotProduct) &&
+    visitor->Visit("maintenance4", &features->maintenance4);
+}
+// clang-format on
+
+template <typename Visitor>
 inline bool Iterate(Visitor* visitor,
                     VkJsonExtDriverProperties* properties) {
   return visitor->Visit("driverPropertiesKHR",
@@ -841,8 +1049,12 @@
 inline bool Iterate(Visitor* visitor, VkJsonDevice* device) {
   bool ret = true;
   switch (device->properties.apiVersion ^
-          VK_VERSION_PATCH(device->properties.apiVersion)) {
+          VK_API_VERSION_PATCH(device->properties.apiVersion)) {
+    case VK_API_VERSION_1_3:
+      ret &= visitor->Visit("core13", &device->core13);
+      FALLTHROUGH_INTENDED;
     case VK_API_VERSION_1_2:
+      ret &= visitor->Visit("core12", &device->core12);
       FALLTHROUGH_INTENDED;
     case VK_API_VERSION_1_1:
       ret &=
@@ -897,16 +1109,22 @@
 template <typename Visitor>
 inline bool Iterate(Visitor* visitor, VkJsonInstance* instance) {
   bool ret = true;
-  switch (instance->api_version ^ VK_VERSION_PATCH(instance->api_version)) {
+  switch (instance->api_version ^ VK_API_VERSION_PATCH(instance->api_version)) {
+    case VK_API_VERSION_1_3:
+      FALLTHROUGH_INTENDED;
     case VK_API_VERSION_1_2:
+      ret &= visitor->Visit("apiVersion", &instance->api_version);
       FALLTHROUGH_INTENDED;
     case VK_API_VERSION_1_1:
       ret &= visitor->Visit("deviceGroups", &instance->device_groups);
       FALLTHROUGH_INTENDED;
     case VK_API_VERSION_1_0:
+      char depString[] =
+          "vkjson is deprecated, and will be replaced in a future release";
       ret &= visitor->Visit("layers", &instance->layers) &&
              visitor->Visit("extensions", &instance->extensions) &&
-             visitor->Visit("devices", &instance->devices);
+             visitor->Visit("devices", &instance->devices) &&
+             visitor->Visit("_comment", &depString);
   }
   return ret;
 }
diff --git a/vulkan/vkjson/vkjson.h b/vulkan/vkjson/vkjson.h
index 52e7bee..88f6e7d 100644
--- a/vulkan/vkjson/vkjson.h
+++ b/vulkan/vkjson/vkjson.h
@@ -33,14 +33,6 @@
 #undef max
 #endif
 
-#ifndef VK_API_VERSION_1_0
-#define VK_API_VERSION_1_0 VK_MAKE_VERSION(1, 0, 0)
-#endif
-
-#ifndef VK_API_VERSION_1_1
-#define VK_API_VERSION_1_1 VK_MAKE_VERSION(1, 1, 0)
-#endif
-
 /*
  * Annotation to tell clang that we intend to fall through from one case to
  * another in a switch. Sourced from android-base/macros.h.
@@ -82,6 +74,16 @@
   VkPhysicalDeviceShaderFloat16Int8FeaturesKHR shader_float16_int8_features_khr;
 };
 
+struct VkJsonCore12 {
+  VkPhysicalDeviceVulkan12Properties properties;
+  VkPhysicalDeviceVulkan12Features features;
+};
+
+struct VkJsonCore13 {
+  VkPhysicalDeviceVulkan13Properties properties;
+  VkPhysicalDeviceVulkan13Features features;
+};
+
 struct VkJsonDevice {
   VkJsonDevice() {
     memset(&properties, 0, sizeof(VkPhysicalDeviceProperties));
@@ -106,6 +108,8 @@
            sizeof(VkPhysicalDeviceSamplerYcbcrConversionFeatures));
     memset(&shader_draw_parameter_features, 0,
            sizeof(VkPhysicalDeviceShaderDrawParameterFeatures));
+    memset(&core12, 0, sizeof(VkJsonCore12));
+    memset(&core13, 0, sizeof(VkJsonCore13));
   }
   VkPhysicalDeviceProperties properties;
   VkPhysicalDeviceFeatures features;
@@ -133,6 +137,8 @@
       external_fence_properties;
   std::map<VkExternalSemaphoreHandleTypeFlagBits, VkExternalSemaphoreProperties>
       external_semaphore_properties;
+  VkJsonCore12 core12;
+  VkJsonCore13 core13;
 };
 
 struct VkJsonDeviceGroup {
diff --git a/vulkan/vkjson/vkjson_instance.cc b/vulkan/vkjson/vkjson_instance.cc
index 5872495..0ffe7e0 100644
--- a/vulkan/vkjson/vkjson_instance.cc
+++ b/vulkan/vkjson/vkjson_instance.cc
@@ -157,76 +157,64 @@
       }
     }
 
-    VkPhysicalDeviceProperties2 properties2 = {
-        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
-        nullptr,
-        {},
-    };
-
     device.subgroup_properties.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES;
-    device.subgroup_properties.pNext = properties2.pNext;
-    properties2.pNext = &device.subgroup_properties;
+    device.subgroup_properties.pNext = properties.pNext;
+    properties.pNext = &device.subgroup_properties;
 
     device.point_clipping_properties.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_POINT_CLIPPING_PROPERTIES;
-    device.point_clipping_properties.pNext = properties2.pNext;
-    properties2.pNext = &device.point_clipping_properties;
+    device.point_clipping_properties.pNext = properties.pNext;
+    properties.pNext = &device.point_clipping_properties;
 
     device.multiview_properties.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES;
-    device.multiview_properties.pNext = properties2.pNext;
-    properties2.pNext = &device.multiview_properties;
+    device.multiview_properties.pNext = properties.pNext;
+    properties.pNext = &device.multiview_properties;
 
     device.id_properties.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES;
-    device.id_properties.pNext = properties2.pNext;
-    properties2.pNext = &device.id_properties;
+    device.id_properties.pNext = properties.pNext;
+    properties.pNext = &device.id_properties;
 
     device.maintenance3_properties.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES;
-    device.maintenance3_properties.pNext = properties2.pNext;
-    properties2.pNext = &device.maintenance3_properties;
+    device.maintenance3_properties.pNext = properties.pNext;
+    properties.pNext = &device.maintenance3_properties;
 
-    vkGetPhysicalDeviceProperties2(physical_device, &properties2);
-
-    VkPhysicalDeviceFeatures2 features2 = {
-        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
-        nullptr,
-        {},
-    };
+    vkGetPhysicalDeviceProperties2(physical_device, &properties);
 
     device.bit16_storage_features.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES;
-    device.bit16_storage_features.pNext = features2.pNext;
-    features2.pNext = &device.bit16_storage_features;
+    device.bit16_storage_features.pNext = features.pNext;
+    features.pNext = &device.bit16_storage_features;
 
     device.multiview_features.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES;
-    device.multiview_features.pNext = features2.pNext;
-    features2.pNext = &device.multiview_features;
+    device.multiview_features.pNext = features.pNext;
+    features.pNext = &device.multiview_features;
 
     device.variable_pointer_features.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTER_FEATURES;
-    device.variable_pointer_features.pNext = features2.pNext;
-    features2.pNext = &device.variable_pointer_features;
+    device.variable_pointer_features.pNext = features.pNext;
+    features.pNext = &device.variable_pointer_features;
 
     device.protected_memory_features.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES;
-    device.protected_memory_features.pNext = features2.pNext;
-    features2.pNext = &device.protected_memory_features;
+    device.protected_memory_features.pNext = features.pNext;
+    features.pNext = &device.protected_memory_features;
 
     device.sampler_ycbcr_conversion_features.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES;
-    device.sampler_ycbcr_conversion_features.pNext = features2.pNext;
-    features2.pNext = &device.sampler_ycbcr_conversion_features;
+    device.sampler_ycbcr_conversion_features.pNext = features.pNext;
+    features.pNext = &device.sampler_ycbcr_conversion_features;
 
     device.shader_draw_parameter_features.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETER_FEATURES;
-    device.shader_draw_parameter_features.pNext = features2.pNext;
-    features2.pNext = &device.shader_draw_parameter_features;
+    device.shader_draw_parameter_features.pNext = features.pNext;
+    features.pNext = &device.shader_draw_parameter_features;
 
-    vkGetPhysicalDeviceFeatures2(physical_device, &features2);
+    vkGetPhysicalDeviceFeatures2(physical_device, &features);
 
     VkPhysicalDeviceExternalFenceInfo external_fence_info = {
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FENCE_INFO, nullptr,
@@ -272,6 +260,38 @@
     }
   }
 
+  if (device.properties.apiVersion >= VK_API_VERSION_1_2) {
+    device.core12.properties.sType =
+        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES;
+    device.core12.properties.pNext = properties.pNext;
+    properties.pNext = &device.core12.properties;
+
+    vkGetPhysicalDeviceProperties2(physical_device, &properties);
+
+    device.core12.features.sType =
+        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
+    device.core12.features.pNext = features.pNext;
+    features.pNext = &device.core12.features;
+
+    vkGetPhysicalDeviceFeatures2(physical_device, &features);
+  }
+
+  if (device.properties.apiVersion >= VK_API_VERSION_1_3) {
+    device.core13.properties.sType =
+        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_PROPERTIES;
+    device.core13.properties.pNext = properties.pNext;
+    properties.pNext = &device.core13.properties;
+
+    vkGetPhysicalDeviceProperties2(physical_device, &properties);
+
+    device.core13.features.sType =
+        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
+    device.core13.features.pNext = features.pNext;
+    features.pNext = &device.core13.features;
+
+    vkGetPhysicalDeviceFeatures2(physical_device, &features);
+  }
+
   return device;
 }